How To Prevent Unnecessary React State Update Re-renders
November 14, 2021 - 5 minutes
The most commonly used hook in React development is useState
. No matter how small or big your React application is, you’ll be using it. It’s the key to implementing interactivity, logic, and state into our React web applications. Without all of these, our web application would be no different from a static web page.
The key feature that enables all of the factors is state updates: updating the internal state based on network requests, user interactivity, user input, and the list goes on. In turn, these state updates will trigger updates to user interfaces, information services, or anything that will reflect the experience of end-users.
However, not every setState
call should always result in a state update re-renders. State updates will trigger a re-render of the component as well as all the children components. This can be expensive for the browser and slow down the React application so much that it negatively affects the experience of users.
Therefore, in certain scenarios, it’s relevant to know how to prevent state updates re-renders. This article will cover several ways to prevent either state updates or re-renders from occurring. Not only will we look into the implementation, but also their differences and use cases. This information will provide you with a solid foundation on this topic and allow you to apply it to your own React code.
Avoid The State Update
The most straightforward approach to preventing state update re-renders is by avoiding the state update entirely. This means making sure that the setState
function is not called unless strictly necessary.
const Component = () => {
const [showImage, setShowImage] = useState(true);
const hideImage = useCallback(() => {
if (showImage) {
setShowImage(false);
}
}, [showImage]);
return (
<Card>
{showImage ? <CardImageCover /> : null}
<CardDescription />
<CardButton onClick={hideImage} />
</Card>
);
};
The main problem with this approach is that it requires you to verify the state itself beforehand. This means that you have to rely on the state object from the original state definition. This is especially relevant when triggering state updates from inside hooks, like useCallback
or useEffect
.
It means that the hook now has to depend on a variable that is outside of its scope. In certain scenarios, this can result in race conditions where the state is not updated properly yet before reaching the actual state update. On top of that, this will also require the state to be added to the hooks’ dependencies list.
In a lot of scenarios, these two factors will not have a significant impact and do not matter. But in certain scenarios, this can cause undesired behaviour as the hook will be triggered more often than desired or the state update is triggered with outdated information.
Update With The Same State
Another approach to preventing state update re-renders is a lesser-known one. It makes use of the internals of the useState
React hook, which does a comparison between the previous and current state values. If both values are the same according to the Object.is
comparison algorithm, then React will bail your component out and make sure no re-renders occur.
This is an extremely convenient mechanic to have when trying to avoid state update re-renders. Unfortunately, you might be less aware of this if you’ve been using React for a longer time. When React development was still mainly using class components, a setState
call would always trigger a re-render. So the most logical solution for preventing re-render was to avoid the state updates entirely. But now, that is not necessary anymore.
const Component = () => {
const [images, setImages] = useState([]);
const hideImage = useCallback((imageUrl) => {
setData((prevData) => {
if (prevData.includes(imageUrl)) {
return prevData.filter(image => image !== imageUrl);
}
return prevData;
});
}, []);
return (
<Section>
{images.map((image) => (
<Card key={image}>
<CardImageCover image={image} />
<CardDescription />
<CardButton onClick={hideImage} />
</Card>
))}
</Section>
);
};
In this approach, you’ll be doing a very similar thing as avoiding the state updates entirely. But instead of verifying the state before the state update, you’ll perform them inside the state update call itself. This requires you to use a function as a state updater instead of a value.
The state updater function receives the current state value and should return the new value that you want. Inside the function, you can use the current state value to check whether the state should actually change. If so, the function should return the appropriate value. If not, the function can return the current state value instead and will bail out of re-renders as previously mentioned.
Due to the way the comparison algorithm works, state updates based on primitive data types will always be optimized. In those scenarios, it’s not necessary to worry about the current state object reference and it’s fine to use concrete values. But when dealing with more complex data structures like arrays and objects, it’s necessary to reuse the current state object when trying to prevent re-renders inside of state updates.
One of the major benefits of this approach is that the state object is not necessary anymore as a dependency for your custom hook. This benefits the performance and avoids creating more entities or executing more code than necessary. On top of that, you’re also guaranteed to work with up-to-date state values as you’re referencing them using a state updater function. These factors together will lead to more optimized React code and less unexpected behaviour.
Final Thoughts
State the most important thing in any React web application nowadays. The useState
hook allows us for easy state management and state updates. However, not every state update should result in a re-render. Unnecessary re-renders are expensive for the browser and negatively impact the experience of the end-user.
To help you with this, this article covered two ways on how to prevent state updates re-renders. These include avoiding the state update entirely and preventing re-renders by triggering the state update with the same existing state. Not only did we look into the implementation, but also the use cases, considerations, drawbacks, and benefits. This information will provide you with a solid foundation on this topic and allow you to apply it to your own React code.