I have an object state inside my react component set up like this:
const [state, setState] = useState({ keyOne: true, keyTwo: 'someKey' })
When I change the state, the component obviously rerenders. But that's the problem. The component is rerendering even if I change the state to the value that it's currently set to. For example, the component still rerenders if I do this:
setState({ keyOne: true, keyTwo: 'someKey' });
I'm guessing this is because I'm passing a new object to setState
, and so it will be interpreted as a different state each time even if the content in the object is the same. So I guess my question is:
How can I prevent a component from re-rendering if the new object state has the same content as the original state? Is there a built-in way to do this, or will I have to compare the objects beforehand myself?
CodePudding user response:
It's rerendering because the new object is a different object from the previous object. It may have all the same properties inside of it, but react only does a shallow comparison.
If you want to skip the render, you will need to make sure the object reference does not change. For example:
setState(prev => {
if (prev.keyOne === true && prev.keyTwo === 'someKey') {
return prev; // Same object, so render is skipped
} else {
return { keyOne: true, keyTwo: 'someKey' };
}
});
If you have some favorite library for checking deep equality (Eg, lodash or ramda), you could shorten it to something like the following. This will be particularly useful if there are a lot of properties you would otherwise need to check:
setState(prev => {
const next = { keyOne: true, keyTwo: 'someKey' };
return R.equals(prev, next) ? prev : next;
});
CodePudding user response:
One option is to use separate states for the different properties of the object. That way, a re-render will only be triggered once one of the new state values is different. For a small example:
const App = () => {
console.log('rendering');
const [val, setVal] = React.useState(true);
React.useEffect(() => {
setVal(true);
}, []);
return 'foo';
};
ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>
As you can see, despite the state setter being called, the component doesn't re-render, because the new state is the same as the old state.
For your code, you'd have something like
const [keyOne, setKeyOne] = useState(true);
const [keyTwo, setKeyTwo] = useState('someKey');
Using an approach like the above instead of putting state into a single object is a very common practice with functional components.