Mike K
Mike K

Reputation: 6473

How to prevent rerenders of whole components in React?

I have a modal component that is a parent to 3 Tab components. The formData is part of the Modal component state, and passes it down to its children. The problem is that every time any field in any tab changes, it triggers a rerender of the whole Modal component and its children, making the form a bit laggy.

I was under the impression that React only rerenders whatever differences exist between the actual DOM and the virtual DOM? Is there a way around this?

Codesandbox minimal example

Upvotes: 3

Views: 674

Answers (1)

HMR
HMR

Reputation: 39250

I have made some changes to your code so contact won't re render when details change and vice versa.

//Wrap functional component Details in React.memo
const Details = React.memo(function Details({
  formData: { title, description },
  onChange,
}) {
  console.log('render details');
  return (
    <React.Fragment>
      <input
        value={title}
        onChange={({ target: { value } }) =>
          onChange(value, 'details', 'title')
        }
      />
      <input
        value={description}
        onChange={({ target: { value } }) =>
          onChange(value, 'details', 'description')
        }
      />
    </React.Fragment>
  );
});
//Make Contact also pure with React.memo
const Contact = React.memo(function Contact({
  formData: { name, number },
  onChange,
}) {
  console.log('render contact');
  return (
    <React.Fragment>
      <input
        value={name}
        onChange={({ target: { value } }) =>
          onChange(value, 'contact', 'name')
        }
      />
      <input
        value={number}
        onChange={({ target: { value } }) =>
          onChange(value, 'contact', 'number')
        }
      />
    </React.Fragment>
  );
});

const Create = () => {
  const [formData, setFormData] = React.useState({
    details: {
      title: '',
      description: '',
    },
    contact: {
      name: '',
      number: '',
    },
  });
  //only create onChange once (empty dependencies for useCallback)
  const onChange = React.useCallback(
    (value, tab, field) => {
      //you were mutating and not using callback for state
      //  setting causing unnecessary dependency for useCallback
      setFormData(current => ({
        ...current,
        [tab]: {
          ...current[tab],
          [field]: value,
        },
      }));
    },
    []
  );

  return (
    <React.Fragment>
      {/* instead of passing the entire formData, only pass
      the part that it actually needs so when Contact changes
      Details isn't re rendered because you're passing an object
      that has changed you pass object containing contact to details
      and object contact details to details */}
      <Details
        formData={formData.details}
        onChange={onChange}
      />
      <Contact
        formData={formData.contact}
        onChange={onChange}
      />
    </React.Fragment>
  );
};
ReactDOM.render(
  <Create />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 3

Related Questions