TL;DR: Optimize your React app with memoization to prevent unnecessary re-renders. Learn to implement memoization using React.PureComponent for class components, React.memo and useMemo for functional components, and useCallback for functions to ensure efficient rendering and a smoother user experience.
Performance optimization is critical for web applications to maintain a solid user experience. In React, there are instances in which re-rendering a complex component leads to performance issues, impacting the user experience. React offers memoization as a performance feature that speeds up component rendering to overcome this hassle.
This article will discuss what memoization is in React and how you can implement it in your React apps.
Memoization is an optimization technique for accelerating computer programs by caching the results of heavy function calls and returning them when similar inputs are encountered repeatedly. Simply, React memoization is similar to caching.
As an example, consider a long-running, sophisticated function that accepts input. To speed up this function execution, you can cache the results after executing it. So that, you can take the cached value without recomputing the value whenever you execute it again with the same inputs. Consequently, we can avoid unwanted rerenders for the same resultant output with the same set of inputs. We can capture the initial render result and cache it in memory for later use. This will boost app performance.
When props within a React functional component change, the whole component rerenders by default. To put it in another way, if a value inside the component changes, the entire component will rerender, along with all the functions or components whose values or props haven’t changed.
This will result in a performance pitfall, which can be avoided with memoization.
Both functional and class components benefit from memoization. React offers HOCs and hooks to implement this feature. We can use React.PureComponent within class components. Memoization for functional components is also possible with React.memo() HOC and useMemo() Hook. The useCallback() Hook is also there for caching functions instead of values.
Now, let’s have a look at these approaches one by one.
React.PureComponent helps us to implement memoization in a class component. PureComponent implements React.ShouldComponentUpdate(), which performs a shallow comparison of state and props and only renders the React component if the props or state have changed.
In this case, we have to keep the props and state as simple as possible. Since PureComponent may yield incorrect results if they include any advanced data structures. As PureComponent’s shouldComponentUpdate() avoids prop updates for the entire component subtree, we should also ensure that all PureComponent’s children are also PureComponent.
Refer to the following simple example to implement memoization with React.PureComponent.
//Parent.js import React, {Component} from 'react'; import Child from './Child'; class Parent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } onClick = () => { this.setState((prevState) => { return { count: prevState.count + 1 }; }); }; render() { console.log("Parent render"); return ( <div className="App"><button onClick={this.onClick}>Update Counter</button> <h2>{this.state.count}</h2> <Child name={"child"} /></div> ); } } export default Parent;
The Child component should be like this.
//Child.js import React, {Component, PureComponent} from 'react'; class Child extends React.PureComponent { render() { console.log("Child render"); return ( <div> <h2>{this.props.name}</h2> </div> ); } } export default Child;
When you click on the Update Counter in the Parent component, it will call both the Parent and Child components’ render methods for the initial render.
Only the Parent component’s render function is invoked for successive rerendering on each update. Therefore, we don’t need the Child component for re-rendering.
Let’s see how to use React.memo() and useMemo() for memoization under this section.
React.memo() is very simple to use because it is identical to React.PureComponent. While React.memo() works with functional components, PureComponent works with class components.
Consider these two components. The Parent component will look like this.
const Parent= () => { const [counter1, setCounter1] = React.useState(0) const [counter2, setCounter2] = React.useState(0) const incrementCounter1 = () => { setCounter1(counter1=> counter1+ 1) } return ( <><button onClick={incrementCounter1}>Increment counter 1</button><Child value={counter1}>Counter 1</Child><Child value={counter2}>Counter 2</Child></> ) }
And the Child component is as follows.
const Child= ({value, child}) => { console.log('Render: ', child) return ( <div> {child}: {value} </div> ) } export default Child;
The state of counter1 Child component updates whenever the user clicks the Increment counter 1 button, leading the Parent to rerender both counter Child components, which can be considered an undesired rerender.
You need to wrap the Child component with React.memo() to avoid this. It will only force the Child component to rerender if the props are changed.
const Child= ({value, child}) => { console.log('Render: ', child) return ( <div> {child}: {value} </div> ) } export default React.memo(Child)
Note: If the component has a useState, useReducer, or useContext Hook, the state or context will still trigger a rerender.
To regulate the memo’s typical behavior, which shallowly compares complex objects in the props object, we have to create a custom comparison function and pass it as the second argument, as follows.
function App(props) { /* render using props */} function areEqual(prevProps, nextProps) { /* if results are same when passing prevProps and when passing nextProps then areEqual will return true, if results are not same then it will return false */} export default React.memo(App, areEqual);
In React, the useMemo() hook is the most fundamental method of memoization. You can use useMemo() to implement memoization if you’re a Hooks lover.
To use useMemo(), pass a function that performs the heavy computation you want to memoize as the first parameter and an array of all dependencies for that memoization as the second argument. useMemo() boosts performance by recalculating the memoized value only if one of the given dependencies changes.
This optimization helps to avoid expensive calculations on every render.
const memoizedValue = useMemo(() => expensiveComputation(a, b), [a, b] )
Let’s consider a simple example for calculating a factorial. When the component renders initially, the useMemo Hook calls FactorialCalc and memoizes the value calculated before returning the result to the component. The significant point here is that if the list of dependencies ([number]) we provide does not update between renders, useMemo will return the memoizedFactorial instead of calling FactorialCalc.
import { useState, useMemo } from "react"; const App = () => { const [number, SetNumber] = useState(1); const memoizedFactorial = useMemo(() => FactorialCalc(number), [number]); const onChange = (e) => { console.log(e.target.value); SetNumber(Number(e.target.value)); }; return ( <div><input type="text" onChange={onChange} /> {memoizedFactorial} </div> ); }; function FactorialCalc(n) { console.log('factorialOf(n) called!'); return n <= 0 ? 1 : n * FactorialCalc(n - 1); } export default App;
Note: Additionally, keep in mind that useMemo() executes during rendering. Thus, we should avoid using whatever isn’t used while rendering, such as side effects.
useCallback() is almost equivalent to useMemo() in that it memoizes a result relying on an array of dependencies. However, useCallback() is only used for memoizing functions rather than values.
When we used the React.memo() in the child component in the previous case, the Child component did not rerender, although the Parent component did. Nevertheless, passing a function as a prop to a Child component would still cause the Child component to rerender, even with React.memo().
Consider the following example.
//Parent.js export default function Parent() { const [counter, setCounter] = useState(0); const handleClick = () => { setCounter(counter + 1); }; const onClick= () => { console.log("onClick"); // This is the new handler that will be passed to the child }; console.log("Parent render"); return ( <div className="App"><button onClick={handleClick}>Increase</button><h2>{counter}</h2><Child name={"child"} childHandler={onClick} /></div> ); }
And the Child component will look like this.
//Child.js export function Child(props) { console.log("Child render"); return ( <div><h2>{props.name}</h2></div> ); } export default React.memo(Child);
Even when the props provided haven’t changed, when we increase the counter in the Parent component, it rerenders the Child component, as well.
So, what triggers the Child component to rerender is that a new onClick function is generated and sent to the Child component each time the Parent component rerenders. Because the onClick function is created with each rerender, the Child detects that the onClick reference has updated and rerenders the Child component solely on a basic comparison of props.
The useCallback helps us to prevent recreating the method each time the parent component is rendered. We have to introduce the useCallback() Hook to the onClick function in this case. We can use an empty, a single, or a list of dependencies as the second argument. A memoized result of the callback provided as the first argument is returned if the dependencies specified in useCallback() do not update.
//Parent.js export default function Parent() { const [counter, setCounter] = useState(0); const handleClick = () => { setCounter(counter + 1); }; const onClick= useCallback(() => { //using useCallback() for the handler function console.log("handler"); }, []); console.log("Parent render"); return ( <div className="App"><button onClick={handleClick}>Increase</button><h2>{counter}</h2><Child name={"joe"} childHandler={onClick} /></div> ); }
The Child component won’t change.
As we used the useCallback() Hook for the Parent onClick, it will not be created for each Parent rerender, and a memoized instance of the onClick will be passed down to the Child.
For more details, refer to the example for Memoization in React on the GitHub repository.
Memoization is a React performance optimization feature that, when utilized correctly, improves application efficiency. To achieve memoization, React provides PureComponent, memo, useMemo , and useCallback. Memoization improves performance by saving function results when the same prop is provided, minimizing the number of re-renderings.
However, excessive usage of memoization in situations with no performance concerns might deteriorate performance. If we utilize it for all components, performance may suffer because it caches results, increasing the amount of memory the app uses.
So, memoization should only be used when there is a noticeable advantage, and you need to make sure that your app is suitable to use these techniques to get the maximum out of them.
I hope you found this article useful. Thank you for reading!
The Syncfusion React suite offers over 70 high-performance, lightweight, modular, and responsive UI components in a single package. It’s the only suite you’ll ever need to construct a complete app.
If you have questions, you can contact us through our support forum, support portal, or feedback portal. We are always happy to assist you!