The original link www.robinwieruch.de/react-memo
React’s memo API can be used to optimize the rendering behavior of the React function component. We’ll start with a sample component to illustrate the problem and then address it via the React Memo API.
Keep in mind that most of the performance optimizations in React are premature. React is fast by default, so every performance optimization is optional in case something starts to slow down.
Note: If your React component is still rendering with React Memo, check out the guide to React’s useCallback Hook. Typically, rerendering is associated with the callback handler and changes with each rendering.
Note: Do not confuse the React Memo API with the React useMemo Hook. React Memo is used to wrap React components to prevent re-rendering, while useMemo is used to remember values.
Let’s look at the following example React application, which renders a list of user items and allows us to add users to the list. We use the React useState Hook to make this list stateful:
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
const App = () => {
const [users, setUsers] = React.useState([
{ id: 'a', name: 'Robin' },
{ id: 'b', name: 'Dennis' },
]);
const [text, setText] = React.useState('');
const handleText = (event) => {
setText(event.target.value);
};
const handleAddUser = () => {
setUsers(users.concat({ id: uuidv4(), name: text }));
};
return (
<div>
<input type="text" value={text} onChange={handleText} />
<button type="button" onClick={handleAddUser}>
Add User
</button>
<List list={users} />
</div>
);
};
const List = ({ list }) => {
return (
<ul>
{list.map((item) => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
};
const ListItem = ({ item }) => {
return <li>{item.name}</li>;
};
export default App;
Copy the code
If you include console.log statements in the function body of the App, List, and ListItem components, you’ll see that these log statements run every time someone types in the input field:
const App = () => { console.log('Render: App'); . }; const List = ({ list }) => { console.log('Render: List'); return ( <ul> {list.map((item) => ( <ListItem key={item.id} item={item} /> ))} </ul> ); }; const ListItem = ({ item }) => { console.log('Render: ListItem'); return <li>{item.name}</li>; };Copy the code
After entering in the input field, all components are re-rendered because the App component updates its state and all its children are re-rendered by default.
// after typing one character into the input field
Render: App
Render: List
Render: ListItem
Render: ListItem
Copy the code
This is the default behavior given by React, and for the most part, you can keep it that way as long as your application doesn’t start feeling sluggish.
But once it starts to feel slow, like rendering a huge list of items every time the user types in an input field, you can use the React Memo API to remember what your component does:
const List = React.memo(({ list }) => {
console.log('Render: List');
return (
<ul>
{list.map((item) => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
});
const ListItem = ({ item }) => {
console.log('Render: ListItem');
return <li>{item.name}</li>;
};
Copy the code
Now, when we enter in the input field, only the App component will be rerendered, because it is the only component affected by the changed state. The List component receives its Memo props from before, and these props are not changed, so they are not rerendered at all. ListItem also doesn’t use the React memo API because the List component already prevents re-rendering.
// after typing one character into the input field
Render: App
Copy the code
In a nutshell, this is the Memo function of React. It seems that we do not need to remember the ListItem component. However, once you add the new project to the list using the button, you will see the following output from the current implementation:
// after adding an item to the list
Render: App
Render: List
Render: ListItem
Render: ListItem
Render: ListItem
Copy the code
By adding items to the List, the List changes, causing the List components to update. Now this is the behavior we want, because we want to render all items (2 items) plus new items (1 item). But perhaps it would be more efficient to render only one new item instead of all items:
const List = React.memo(({ list }) => {
console.log('Render: List');
return (
<ul>
{list.map((item) => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
});
const ListItem = React.memo(({ item }) => {
console.log('Render: ListItem');
return <li>{item.name}</li>;
});
Copy the code
After trying the previous scenario, by adding an item to the list using the new implementation of the React Memo feature, you should see the following output:
// after adding an item to the list
Render: App
Render: List
Render: ListItem
Copy the code
Present only new projects. All previous items in the list remain unchanged and therefore will not be re-rendered. Only components affected by state are now rerendered.
You may be wondering why React Memo is not used on all components, or why React Memo is not the default setting for all React components.
Internally, the Memo function of React must compare the previous props to the new props to determine whether the component should be rerendered. Often, the calculation of this comparison can be more expensive than a new render component.
In short, React memos work best when your React components are slow and you want to improve their performance. This typically occurs in components with large amounts of data, such as huge lists, where many components must be re-rendered once a single data point changes.