Published on November 8, 2024
React memo is a powerful feature that improves the performance of your React applications by letting you skip the re-rendering of components that haven't changed.
This article explains how React memo works and how to use it. It also includes use cases and code examples that you can adapt for use in your own React code to make sure that your applications are as fast and efficient as possible.
Memoization is a programming technique that improves performance by caching the results of a function. Code that implements memoization checks the result of a function each time it is called to see if it can serve the cached result and avoid recomputation.
In the context of React, this means storing the results of rendering a component to avoid excessive re-renders. React memo is a component that allows you to wrap React components and memoize them based on their properties (props). This means that components wrapped in a memo
will not be re-rendered if their properties have not changed.
Typically in React, when the state is updated in a parent component, this will trigger a re-render of that component along with any of its child components, even if only some of them rely on the updated state. This can lead to poor performance, especially if some of those components make API calls or perform complex calculations.
This is solved by using React memo: When you wrap one of your components with a memo
component, it will shallowly compare its current props to its previous ones, and if there is no change in the props, React will skip re-rendering that component and continue to use the existing result (as it was in the previous virtual DOM).
For example, if you have a React parent component that displays the value of the U.S. dollar in different currencies (each currency conversion being in its own child component), each time the state of the parent component is updated with new currency data, every child component is re-drawn — even if the conversion rate hasn't changed. By wrapping each child component with memo
, only those where the calculated value has actually changed will be redrawn, improving performance.
Shallow comparison – If a prop is a primitive (string or number), React memo
will check if the new value is different from the old one. If a prop is an object or an array, React memo
will only compare the memory reference; even if the contents are the same but the reference has changed, it will consider them different and trigger a re-render.
Deep comparison – If a prop is complex, such as an array or object, comparison would require checking every nested value within to ensure all properties are equal to the old values. While this would provide an exact match, it’s computationally expensive and can be slower than the actual re-renders you are trying to prevent. Deep comparison should be avoided by using composition-based patterns to move the state closer to where it’s needed, minimizing the need for complex prop comparisons.
The benefits of using React memo to optimize your components include:
Preventing unnecessary re-renders and avoiding redundant updates.
Improving performance.
Optimizing expensive and complex components.
Enhancing the user experience.
As memo is simple to implement by wrapping your existing React components, it is an effective way to improve the efficiency of your apps without significantly reworking them.
A visual comparison showing how React memo optimizes re-renders in a component tree.
Now that you know what React memo is and how it works, let's take a look at some code and how you can use it in an application.
In your React project, create a parent component:
Here the component is initialized by creating some mock data, which will render out a list of child components. With 5,000 components, other optimization techniques could be used here (pagination, for example), but this is just to demonstrate a clearly noticeable performance lag.
This component also includes some functionality that allows the user to click a child component from the list that will add its id to an array. This is checked later to decide if it has been clicked or not.
Next, create a child component:
This is just a simple card component with some code to simulate some sort of expensive computation in each list item.
Now, if you run the application, you will be able to see a noticeable delay in performance when you click a card to change its color. If you open up the console window in your browser, you will also be able to see that each child component logs a message every time you click, showing that updating one component is triggering a re-render for all children.
Now let's add the fix with React memo. Add the below code in App.js
above the ParentComponent
.
Create a React memo component by wrapping the child in the React.memo
component. By default, React.memo
does a shallow comparison of props. This would normally be sufficient, but because the prop isClicked
is generated dynamically (by using .some
to generate a boolean), you have to pass a second argument to React.memo
with an equality check function, allowing you to customize the way properties are checked. This function opens up the possibility to check objects deeply.
This is one of the common pitfalls with using React memo. The bottom line should be to either ensure that comparison props are simple values such as numbers or strings, or use a custom equality check for complex props.
Now add the following function above the renderWithoutMemo
function:
In the return statement, remove the current renderwithoutMemo()
and replace it with renderWithMemo()
. Now, if you refresh the application, you should see a notable improvement in the load speed when you click on a card. Also, in the console, you will only see logs from the cards you click on, showing that the other child components have been memoized. The only components being re-rendered now are the components whose props have changed.
Here you can access a working demo of the application where you can switch between renderWithMemo()
and renderWithoutMemo()
to see the performance lag.
It is considered best practice to use React memo in the following scenarios:
Frequent prop changes: If the props of a parent component change frequently but the output of a child component stays the same.
Expensive rendering: If you have some complex calculation going on inside a component.
Large list of components: If you have a large list of components, especially if the props are changing frequently in a parent component or you have expensive computation inside each child component.
Pure function components: Components that render often and rely solely on the input props to determine their rendering output and have no internal state or side effects.
If none of the above scenarios are true, React memo will have negligible benefits and may add unnecessary complexity to your application. To take one of the examples above, if you use React memo on a component that usually renders with different props, React will do two jobs; it will invoke the comparison function to determine whether the previous and next props are equal, and because the props comparison will always return false in this scenario, React will re-render the component anyway. So, you will get no performance benefits, and this will actually result in more overhead than benefit.
You should use the profiler tool provided by React to identify any performance issues. The Profiler tool allows you to measure how often a component renders and how long these renders take, helping you determine if memoization would provide actual performance improvements. By profiling first, you can make informed decisions about when React memo is truly beneficial, avoiding needless optimizations.
React provides a couple of other related performance optimization tools. While React memo
focuses on optimizing whole component re-renders, these two are a little more granular as they require you to optimize specific parts of your components:
useMemo()
: In React, useMemo is a hook that can wrap computationally heavy functions to memoize the result, so if the function is called again with the same arguments, the result doesn't need to be recalculated each time.
useCallback()
: This hook can be used to wrap a function to memoize its reference. It takes a set of dependencies that determine whether the function should be redefined; if the dependencies remain the same, the memoized function can be reused. It's used to prevent unnecessary re-creation of functions that are passed down as props.
According to the Doherty threshold, applications that respond in 400 ms or under keep a user's attention and increase engagement — and anything over that causes frustration and may result in users leaving your application altogether.
As your React application scales and grows in complexity, you may find that it starts to slow down as you add more functionality and complex components. This is why it is advisable to employ optimization strategies such as memoization with React memo, pagination, or lazy loading from the beginning of your project, so that it always performs as well as possible.
Frontend optimizations can only go so far in applications that rely on online APIs to retrieve data — for engaging digital experiences, you need to be able to deliver optimized content to users' devices, whatever and wherever they are.
Contentful provides a scalable, high-performance composable content platform with a unified interface for defining and managing the content for your omni-channel campaigns and app back ends. Connect your apps using our REST and GraphQL APIs, and give your creatives powerful collaborative tools including live previews to optimize the production and delivery of your content.
Subscribe for updates
Build better digital experiences with Contentful updates direct to your inbox.