useState
always triggers an update even when the data's values haven't changed.
Here's a working demo of the problem: demo
I'm using the useState
hook to update an object and I'm trying to get it to only update when the values in that object change. Because React uses the Object.is comparison algorithm to determine when it should update; objects with equivalent values still cause the component to re-render because they're different objects.
Ex. This component will always re-render even though the value of the payload stays as { foo: 'bar' }
const UseStateWithNewObject = () => {
const [payload, setPayload] = useState({});
useEffect(
() => {
setInterval(() => {
setPayload({ foo: 'bar' });
}, 500);
},
[setPayload]
);
renderCountNewObject += 1;
return <h3>A new object, even with the same values, will always cause a render: {renderCountNewObject}</h3>;
};
Is there away that I can implement something like shouldComponentUpdate
with hooks to tell react to only re-render my component when the data changes?
React this.setState, and useState does not make changes directly to the state object. React this.setState, and React.useState create queues for React core to update the state object of a React component.
React this.setState, and React.useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That’s why changes don’t feel immediate. Even if you add a setTimeout function, though the timeout will run after some time.
To understand how to manage an object’s state, we must update an item’s state within the object. In the following code sample, we’ll create a state object, shopCart, and its setter, setShopCart. shopCart then carries the object’s current state while setShopCart updates the state value of shopCart:
So the process to update React state is asynchronous for performance reasons. That’s why changes don’t feel immediate. Even if you add a setTimeout function, though the timeout will run after some time.
If I understand well, you are trying to only call setState
whenever the new value for the state has changed, thus preventing unnecessary rerenders when it has NOT changed.
If that is the case you can take advantage of the callback form of useState
const [state, setState] = useState({});
setState(prevState => {
// here check for equality and return prevState if the same
return {...prevState, ...updatedValues};
});
Here is a custom hook (in TypeScript) that does that for you automatically. It uses isEqual
from lodash. But feel free to replace it with whatever equality function you see fit.
import { isEqual } from 'lodash';
import { useState } from 'react';
const useMemoizedState = <T>(initialValue: T): [T, (val: T) => void] => {
const [state, _setState] = useState<T>(initialValue);
const setState = (newState: T) => {
_setState((prev) => {
if (!isEqual(newState, prev)) {
return newState;
} else {
return prev;
}
});
};
return [state, setState];
};
export default useMemoizedState;
Usage:
const [value, setValue] = useMemoizedState('some initial value');
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With