skip to Main Content

I have been building Login page and facing this problem.

I have multiple component like <Section/>, <Container/>, <FormLayout/>, <InputText/>.

Login page


export default function Login() {
  const [mail,setMail] = useState('');
  const handleMailChange = (e) => {
    setMail(e.target.value);
  };
  return (
    <Section className="someClassName">
      <Container size="l" wrapper={true}>
        <section className="someClassName">
          <Container size="s" className="someClassName">
            <FormLayout arg1="" arg2="">
              <InputText
                type="email"
                value={mail}
                onChange={handleMailChange}
              />
            </FormLayout>
          </Container>
        </section>
      </Container>
    </Section>
  );
}


Components are defined like this in other files


const Section = ({ className = "", children }) => {
  return <div>{children}</div>;
};

const Container = ({ size='', wrapper={false}, children }) => {
  return <div>{children}</div>;
};

const InputText = ({ value = "", type = "text", onChange = "" }) => {
  const [text, setText] = useState(value);

  const handleChange = useCallback(
    (event) => {
      setText(event.target.value);
      if (typeof onChange === "function") {
        onChange(event);
      }
    },
    [onChange]
  );

  return <input value={text} type={type} onChange={handleChange} />;
};

I found that every time I type any character Section re-renders and input box is out of the focus. How can i fix this problem ?

Edit: I was able to generate this problem here.

4

Answers


  1. It looks like you’re trying to make InputText controlled and both uncontrolled at the same time, this may be what is causing your issue.

    There is no need to keep a state value of input as it is now, rather just make it this:

    const InputText = ({ value, type = "text", onChange }) => {
      return <input value={value} type={type} onChange={onChange} />;
    };
    

    Notice that I’m not defaulting these values, so if you still want to give it uncontrolled functionality it will work.

    If there’s another reason you need to keep local state of that input control, then you need to provide that context in the question.

    Login or Signup to reply.
  2. It is re-rendering because the input field is dependent on mail state. And whenever value change, the state update so it re-render.

    And if you want to avoid this behavior use useRef hook.

    Its general practice to use useRef hook with form fields (input, select etc) so that you don’t need to keep updating the value of Field.

    Here is the updated code for the InputText:-

    import useRef from 'react';
    
    const InputText = ({ type = "text" }) => {
        const inputRef = useRef();
    
        return <input 
            ref={inputRef}
            value={text} 
            type={type}
        />;
    };
    
    Login or Signup to reply.
  3. The problem is that you declare nested components like:

    const Section = ({ color = "", currentClass = "", children }) => {
      const Element = () => {
        if (color === "gray") {
          return (
            <div className={[styles.section, styles.gray, currentClass].join(" ")}>
              {children}
            </div>
          );
        } else {
          return (
            <div className={[styles.section, currentClass].join(" ")}>
              {children}
            </div>
          );
        }
      };
    
      return <Element />;
    };
    

    This is a bad practice. Every time the Section component is rendered a new Element component is created and React can’t keep track of the tree.

    The easiest fix is to change the nested component to functions e.g. <Element /> to Element()

    Sections component:

    const Section = ({ color = "", currentClass = "", children }) => {
      const Element = () => {
        if (color === "gray") {
          return (
            <div className={[styles.section, styles.gray, currentClass].join(" ")}>
              {children}
            </div>
          );
        } else {
          return (
            <div className={[styles.section, currentClass].join(" ")}>
              {children}
            </div>
          );
        }
      };
    
      return Element();
    };
    

    You have to do the same with Container

    Here is the working code:

    Edit LongIn React Page (forked)

    You can also move the nested components outside of the parent component

    Login or Signup to reply.
  4. Extending on @Konrad’s answer.

    const Section = ({ color = "", currentClass = "", children }) => {
      const Element = () => {
        if (color === "gray") {
          return (
            <div className={[styles.section, styles.gray, currentClass].join(" ")}>
              {children}
            </div>
          );
        } else {
          return (
            <div className={[styles.section, currentClass].join(" ")}>
              {children}
            </div>
          );
        }
      };
    
      return <Element />;
    };
    

    This is the problem component and you have the same problem in the Container component which is a little more complex.

    The problem is that these Element components are recreated every time a re-render occurs and that will take away focus from the children. It seems that in both of your cases the only difference is the class names, so instead of creating a full new component to render based off prop changes, rather just store the effective changed prop values that you’re passing down.

    Like this:

    const Section = ({ color = "", currentClass = "", children }) => {
      const effectiveClassArray = color === "gray" ?
        [styles.section, styles.gray, currentClass] :
        [styles.section, currentClass];
    
      return (
        <div className={effectiveClassArray.join(" ")}>
          {children}
        </div>
      );
    };
    

    Don’t forget to do the same thing to your Container component.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search