onChange setState is rerendering all components?

Viewed 352

I have a webpage with multiple forms. Here's a bare minimum example of the structure:

export default function Example() {
  const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
    {}
  );
  const [formA, setFormA] = useState({});
  const [formB, setFormB] = useState({});
  const router = useRouter();

  useEffect(() => {
    fetchFormFromWeb(router.query.id).then((previousForm) => {
      console.log("fetched info from web");
      setPreviousFormFetchedFromWeb(previousForm);
      setFormA(previousForm.formA);
      setFormB(previousForm.formB);
    });
  },[router.isReady]);

  return (
    <>
      <FormA form={formA} />
      <FormB form={formB} />
    </>
  );
}

function FormA({ form }) {
  return (
    <input
      type="text"
      name="field1"
      id="field1"
      value={formA.field1}
      onChange={(e) => {
        setFormA(e.target.value);
      }}
    />
  );
}
function FormB({ form }) {
  return (
    <input
      type="text"
      name="field2"
      id="field2"
      value={formB.field2}
      onChange={(e) => {
        setFormB(e.target.value);
      }}
    />
  );
}


I think this should be the encapsulated logic of my form page. The problem is that when the onChange event is called for a field of any form, all forms get re-rendered. I assumed that setState should re-render only the components with the affected dependency change. Am I missing something?

2 Answers

Anytime state of <Example /> is updated, <Example /> re-renders, which in turn also re-renders <FormA /> and <FormB />. This is expected.

You should look into using React.memo() for FormA and FormB if you want them to only re-render when the props passed to them is changed.

Any state change in Example component will trigger re render to its child components (FormA, FormB). If you want to avoid that. wrap FormA and FormB in React.memo. That way you can prevent unwanted re-renders

export default function Example() {
  const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
    {}
  );
  const [formA, setFormA] = useState({});
  const [formB, setFormB] = useState({});
  const router = useRouter();

  useEffect(() => {
    fetchFormFromWeb(router.query.id).then((previousForm) => {
      console.log("fetched info from web");
      setPreviousFormFetchedFromWeb(previousForm);
      setFormA(previousForm.formA);
      setFormB(previousForm.formB);
    });
  },[router.isReady]);

  return (
    <>
      <MemFormA form={formA} />
      <MemFormB form={formB} />
    </>
  );
}

const MemFormA = React.memo(function FormA({ form }) {
  return (
    <input
      type="text"
      name="field1"
      id="field1"
      value={formA.field1}
      onChange={(e) => {
        setFormA(e.target.value);
      }}
    />
  );
})

const MemFormB = React.memo(function FormB({ form }) {
  return (
    <input
      type="text"
      name="field2"
      id="field2"
      value={formB.field2}
      onChange={(e) => {
        setFormB(e.target.value);
      }}
    />
  );
})
Related