In this tutorial, we’ll learn how to implement memoization in React. Memoization improves performance by storing the results of expensive function calls and returning those cached results when they’re needed again.
We’ll cover the following:
- how React renders the UI
- why there’s a need for React memoization
- how we can implement memoization for functional and class components
- things to keep in mind regarding memoization
This article assumes you have a basic understanding of class and functional components in React. If you’d like to brush up on those topics, check out the official React docs on components and props.
How React Renders the UI
Table of Contents
Before going into the details of memoization in React, let’s first have a look at how React renders the UI using a virtual DOM.
The regular DOM basically contains a set of nodes represented as a tree. Each node in the DOM is a representation of a UI element. Whenever there’s a state change in your application, the respective node for that UI element and all its children get updated in the DOM and then the UI is re-painted to reflect the updated changes.
Updating the nodes is faster with the help of efficient tree algorithms, but the re-painting is slow and can have a performance impact when that DOM has a large number of UI elements. Therefore, the virtual DOM was introduced in React.
This is a virtual representation of the real DOM. Now, whenever there’s any change in the application’s state, instead of directly updating the real DOM, React creates a new virtual DOM. React then compares this new virtual DOM with the previously created virtual DOM to find the differences that need to be repainted.
Using these differences, the virtual DOM will update the real DOM efficiently with the changes. This improves performance, because instead of simply updating the UI element and all its children, the virtual DOM will efficiently update only the necessary and minimal changes in the real DOM.
Why We Need Memoization in React
In the previous section, we saw how React efficiently performs DOM updates using a virtual DOM to improve performance. In this section, we’ll look at a use case that explains the need for memoization for further performance boost.
We’ll create a parent class that contains a button to increment a state variable called count
. The parent component also has a call to a child component, passing a prop to it. We’ve also added console.log()
statements in render the method of both the classes:
//Parent.js class Parent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } handleClick = () => { this.setState((prevState) => { return { count: prevState.count + 1 }; }); }; render() { console.log("Parent render"); return ( <div className="App"> <button onClick={this.handleClick}>Increment</button> <h2>{this.state.count}</h2> <Child name={"joe"} /> </div> ); } } export default Parent;
The complete code for this example is available on CodeSandbox.
We’ll create a Child
class that accepts a prop passed by the parent component and displays it in the UI:
//Child.js class Child extends React.Component { render() { console.log("Child render"); return ( <div> <h2>{this.props.name}</h2> </div> ); } } export default Child;
Whenever we click the button in the parent component, the count value changes. Since this is a state change, the parent component’s render method is called.
The props passed to the child class remain the same for every parent re-render, so the child component should not re-render. Yet, when we run the above code and keep incrementing the count, we get the following output:
Parent render Child render Parent render Child render Parent render Child render
You can increment the count for the above example yourself in the following sandbox and see the console for the output:
From this output, we can see that, when the parent component re-renders, it will also re-render the child component — even when the props passed to the child component are unchanged. This will cause the child’s virtual DOM to perform a difference check with the previous virtual DOM. Since we have no difference in the child component — as the props are the same for all re-renders — the real DOM isn’t updated.
We do have a performance benefit where the real DOM is not updated unnecessarily, but we can see here that, even when there was no actual change in the child component, the new virtual DOM was created and a difference check was performed. For small React components, this performance is negligible, but for large components, the performance impact is significant. To avoid this re-render and virtual DOM check, we use memoization.
Memoization in React
In the context of a React app, memoization is a technique where, whenever the parent component re-renders, the child component re-renders only if there’s a change in the props. If there’s no change in the props, it won’t execute the render method and will return the cached result. Since the render method isn’t executed, there won’t be a virtual DOM creation and difference checks — thus giving us a performance boost.
Now, let’s see how to implement memoization in class and functional React components to avoid this unnecessary re-render.
Implementing Memoization in a Class Component
To implement memoization in a class component, we’ll use React.PureComponent. React.PureComponent
implements shouldComponentUpdate(), which does a shallow comparison on state and props and renders the React component only if there’s a change in the props or state.
Change the child component to the code shown below:
//Child.js class Child extends React.PureComponent { // Here we change React.Component to React.PureComponent render() { console.log("Child render"); return ( <div> <h2>{this.props.name}</h2> </div> ); } } export default Child;
The complete code for this example is shown in the following sandbox:
The parent component remains unchanged. Now, when we increment the count in parent component, the output in the console is as follows:
Parent render Child render Parent render Parent render
For the first render, it calls both parent and child component’s render method.
For subsequent re-render on every increment, only the parent component’s render
function is called. The child component isn’t re-rendered.
Continue reading How to Implement Memoization in React to Improve Performance on SitePoint.