Reputation: 6473
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
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