What is React memo and how to use it?
In this article, you will learn what React Memo means, what it does, how it works, and when or when not to use it. You will also learn how it works with detailed examples and codes.
One of the benefits of using React is it’s improved performance, which allows your web applications to load faster and allows you to navigate from one page to another without having to wait so long. This built-in performance optimization has some drawbacks that you can work on to improve the performance of your web applications.
One of the most common and reliable methods for optimizing the performance of your React application is to "memoize" the code you write in your React components using React Memo. This allows you to avoid unnecessary re-renders, enhancing the performance of your React application.
What is Memoization?
Memoization is a form of caching used to store results of expensive functions and avoid repeated calls, leading to repetitive computation of results.
In this article, you will learn what React Memo means, what it does, how it works, and when or when not to use it. You will also learn how it works with detailed examples and codes.
What is React Memo?Anchor
Components in React are designed to re-render whenever the state or props value changes. However, this can impact your application's performance because, even if the change is only intended to affect the parent component, every other child component attached to the parent component will re-render. When a parent component re-renders, so do all of its child components.
React Memo is a higher-order component that wraps around a component to memoize the rendered output and avoid unnecessary renderings. This improves performance because it memoizes the result and skips rendering to reuse the last rendered result.
There are two ways you can wrap your component with React.memo()
. It is either you wrap the actual component directly without having to create a new variable to store the memoized component:
const myComponent = React.memo((props) => {/* render using props */});export default myComponent;
Another option is to create a new variable to store the memoized component and then export the new variable:
const myComponent = (props) => {/* render using props */};export const MemoizedComponent = React.memo(myComponent);
In the example above, myComponent
outputs the same content as MemoizedComponent
, but the difference between both is that MemoizedComponent
render is memoized. This means that this component will only re-render when the props change.
When to use React MemoAnchor
You now know what it means to memoize a component and the advantages of optimization. But this doesn’t mean you should memoize all your components to ensure maximum performance optimization of performance 🙃.
It is important to know when and where to memoize your component else it would not fulfill its purpose. For example, React Memo is used to avoid unnecessary re-renders when there is no change to the state or context of your component. If the state and content of your component will ALWAYS change, React Memo becomes useless. Here are other points:
- Use React Memo if your component will render quite often.
- Use it when your component often renders with the same props. This happens to child components who are forced to re-render with the same props whenever the parent component renders.
- Use it in pure functional components alone. If you are using a class component, use the React.PureComponent.
- Use it if your component is big enough (contains a decent amount of UI elements) to have props equality check.
How to use React MemoAnchor
At this point, you now understand when to use React Memo and what it does. The major point is that you should use it when your component often renders with the same props.
Suppose you have a React application such as a Todo application in which you decide to break up the application into separate components to ensure it is easy to understand and use. You will have the parent component that holds your todo
array in a state. The todo
array will be passed as a prop to the Todo
component:
<!-- App.js -->import React, { useState } from 'react';import Todo from './Todo';const App = () => {console.log('App component rendered');const [todo, setTodo] = useState([{ id: 1, title: 'Read Book' },{ id: 2, title: 'Fix Bug' },]);const [text, setText] = useState('');const addTodo = () => {let newTodo = { id: 3, title: text };setTodo([...todo, newTodo]);};return (<div><inputtype="text"value={text}onChange={(e) => setText(e.target.value)}/><button type="button" onClick={addTodo}>Add todo</button><Todo list={todo} /></div>);};export default App;
In the component above, there is a state to hold all the todo
in an array, and then there is a form you would use to add a new todo to the array. Notice that at the beginning of the component, a console.log()
statement will be implemented once your application renders.
The todo
items are to be passed as props to the Todo
component, which has been imported. This means that the Todo
component is a child component of the App component. In the Todo
component, the todo
array that has been passed as a prop named list will be iterated so that you can access each item in the array and display:
<!-- Todo.js -->import React from 'react';import TodoItem from './TodoItem';const Todo = ({ list }) => {console.log('Todo component rendered');return (<ul>{list.map((item) => (<TodoItem key={item.id} item={item} />))}</ul>);};export default Todo;
In the code above, a console.log()
statement will display a text to show when the Todo
component renders or re-renders. In the component above, each todo
item is passed as a prop to a TodoItem
component. This is done to properly explain what happens and when you need to memoize a component:
<!--TodoItem.js -->const TodoItem = ({ item }) => {console.log('TodoItem component rendered');return <li>{item.title}</li>;};export default TodoItem;
The component above also has a console.log statement to help you know when it renders and re-renders. When you run your application and check the console, you will notice that all three components render.
Our component renders four times, once for the parent component (App.js), once for the Todo
component, and finally, the TodoItem
component renders twice because it has two elements. This means altogether, the application renders four times. This is normal for the initial render. But when you change something in the parent component that doesn’t affect the child components, only the parent component should be rendered.
For example, when you type anything in the text field, the App component and the Todo
component will always render even though the input does not affect the Todo
component. Also, if you have more todo
items, the application will render more times, leading to poor performance.
Optimizing components with React MemoAnchor
Let’s now memoize some of these components so that they only render when there is a change in props. The first component that would be memoized is the Todo
component. It is important to stop re-renders whenever a user types in the text field because it affects the state.
You can do this by wrapping the Todo
component with React.memo()
:
<!-- Todo.js -->// Memoized Todo componentimport React from 'react';import TodoItem from './TodoItem';const Todo = React.memo(({ list }) => {console.log('Todo component rendered');return (<ul>{list.map((item) => (<TodoItem key={item.id} item={item} />))}</ul>);});export default Todo;
Now that the Todo
component is memoized, only the <App />
component will re-render whenever the state changes because it's the only component affected. The <Todo />
component will only re-render when the todo
state changes, affecting the list prop.
Let’s now take a further step to avoid unnecessary re-rendering whenever an item is added to the todo array. When the todo
state changes affecting the list prop, the TodoItem
component will render for each item added. This can become bad when you have so many items.
You would want a situation where once an item is rendered, it won’t re-render; instead, only the new item added will cause the component to render. You can achieve this by memoizing the TodoItem
component:
<!--TodoItem.js -->// Memoized TodoItem componentimport React from 'react';const TodoItem = React.memo(({ item }) => {console.log('TodoItem component rendered');return <li>{item.title}</li>;});export default TodoItem;
Now when you load your application, all the components will render, then the components will now only render when there is a change in the prop. This is done by making a shallow comparison to know if the value changes.
How to use custom comparison function with React MemoAnchor
React Memo makes a shallow comparison and might not function as you wish in some scenarios. If you want control over the comparison, you can provide a custom comparison function as the second argument.
For example, if you are passing an object containing user details as a prop to a Profile
component:
<!-- App.js -->import { useState } from 'react';import Profile from './Profile';const App = () => {console.log('App rendered');const [text, setText] = useState('');let user = { name: 'John Doe', age: 23, userName: 'Jonny' };return (<div><inputtype="text"value={text}onChange={(e) => setText(e.target.value)}/><Profile userDetails={user} /></div>);};export default App;
The memoized Profile component will always render even when the user
object does not change:
<!-- Profile.js -->import React from 'react';const Profile = React.memo(({ userDetails }) => {console.log('profile rendered');return (<div><p>{userDetails.name}</p><p>{userDetails.age}</p><p>{userDetails.userName}</p></div>);});export default Profile;
React Memo doesn't work because it only performs a shallow comparison of the component's properties. Every time the app is updated, the user
variable is re-declared, which happens when you use objects. To fix this, use the second argument and provide a custom comparison function.
<!-- Profile.js -->import React from 'react';const Profile = React.memo(({ userDetails }) => {console.log('profile rendered');return (<div><p>{userDetails.name}</p><p>{userDetails.age}</p><p>{userDetails.userName}</p></div>);},(prevProps, nextProps) => {if (prevProps.userDetails.name === nextProps.userDetails.name) {return true; // props are equal}return false; // props are not equal -> update the component});export default Profile;
When to avoid using React MemoAnchor
You now understand how to use React Memo, but it is important to note that you should not use it in all situations because it does not always work as expected. Performance-related changes that are implemented incorrectly can even harm performance.
When you need to remember the values of a function or an object, you can use hooks like useMemo()
and useCallback()
. Also, avoid using React Memo if the component is light and renders with multiple props.
Finally, never use React Memo to wrap a class-based component; instead, extend PureComponent or implement the shouldComponentUpdate()
method.
Wrapping UpAnchor
You've learned how, why, and when to use React Memo in this article. You've also learned that using React Memo correctly prevents unnecessary re-renderings when the next props are equal to the previous ones.
Have fun coding!