Preventing unnecessary re-evaluation with React memo

It's needless to say that React is highly optimized. So, in a lot of apps and especially in bigger apps you still need to add some optimizations. And, therefore, you as a developer can tell React that it should only re-execute a component under certain circumstances. And those circumstances would be that the props, which the components receive, are changed for example.

To add this optimization we need to use React.memo(). So React.memo allows us to optimize functional components. Now, what does memo do? It tells React that for our component, which it gets as a argument, React should look at the props this components gets and check the new value for all those props and compare it to the previous value those props got. And only if the value of a prop changed, the component should be re-executed and re-evaluated. And if the parent component changed but the prop values for that component here did not change, component execution will be skipped. Let's see an example:

App.js:

import React, { useState } from 'react'; import Button from './components/UI/Button/Button'; import DemoOutput from './components/Demo/DemoOutput'; import './App.css'; function App() { const [showParagraph, setShowParagraph] = useState(false); console.log('APP RUNNING'); const toggleParagraphHandler = () => { //setShowParagraph(!showParagraph); // this also works setShowParagraph(prevShowParagraph => !prevShowParagraph); } return ( <div className="app"> <h1>Hi there!</h1> <DemoOutput show={showParagraph} /> <Button onClick={toggleParagraphHandler}>Toggle Pharagraph!</Button> </div> ); } export default App;

DemoOutput.js:

import React from 'react'; import MyParagraph from './MyParagraph'; const DemoOutput = (props) => { console.log('DemoOutput RUNNING'); return <MyParagraph>{props.show ? 'This is new!' : ''}</MyParagraph>; }; export default React.memo(DemoOutput);

In the above example, the DemoOutput component is executed just when the showParagraph state is changed in App component. The memo method here tells React that whenever the App component changed, it should go to the DemoOutput component and compare the new prop values to the previous prop values, so therefore React needs to do two things: it needs to store the previous prop values, and it needs to make that comparison and that, of course, also has its own performance cost and it, therefore, greatly depends on the component you're applying this to whether it's worth it or not because you're trading the performance cost of re-evaluating the component for the performance cost of comparing props. And it's impossible to say which cost is higher because it depends on the number of props you have and on the complexity of your component and the number of child components your component has. Of course, React.memo can be a great tool if you have a huge component tree with a lot of child components and on a high level in the component tree, you can avoid unnecessary re-render cycles for the entire branch of component tree.

For small apps, for small component trees, and so on, for all of that, it might simply not worth it to add this. But for larger apps where you can cut off entire branches of unnecessary re-evaluations, it might very well be worth it. You just don't wanna wrap every component with React.memo(). Instead, you wanna pick some key parts in your component tree which allows you to cut off an entire branch of child components. That's way more effective than doing this on every child component.

React.memo is useless for components that receive objects or arrays or functions through their props, because even if we add React.memo for this components they will be called because when React compoares the actual value of an object, function or array it compares the reference and the reference will be different on every function call, because they are created again. Let's look at the following example:

App.js:

import React, { useState } from 'react'; import Button from './components/UI/Button/Button'; import DemoOutput from './components/Demo/DemoOutput'; import './App.css'; function App() { const [showParagraph, setShowParagraph] = useState(false); console.log('APP RUNNING'); const toggleParagraphHandler = () => { //setShowParagraph(!showParagraph); // this also works setShowParagraph(prevShowParagraph => !prevShowParagraph); } return ( <div className="app"> <h1>Hi there!</h1> <DemoOutput show={false} /> <Button onClick={toggleParagraphHandler}>Toggle Pharagraph!</Button> </div> ); } export default App;

Button.js:

import React from 'react'; import classes from './Button.module.css'; const Button = (props) => { console.log('BUTTON running'); return ( <button type={props.type || 'button'} className={`${classes.button} ${props.className}`} onClick={props.onClick} disabled={props.disabled} > {props.children} </button> ); }; export default React.memo(Button);

In the above example, the Button component is executed at every button click because even if we added React.memo, we send a function as prop on the Button component.

Category: React Tags: #react, #performance