Probably you have already reached some level at React - you have an understanding of what state and props are, how to use basic React hooks - useState, useEffect.
And maybe, you've started to notice that sometimes your React components work very slow (especially the heavy ones with a lot of UI elements and other components)
And you started to ponder on how to fix it and optimize the performance...
After some research, you stumbled upon something called React memo.
You could ask yourself: What the heck is that ?
So, React memo is a HOC - higher-order component that allows you to improve the performance of your React app.
It skips rendering the component if passed props have not changed.
How does it work?
Super simple. A memo will just memoize the rendered output of the component and before the next render, it will compare props.
If nothing changed, the memo will just reuse the last rendered output.
Let me show you an easy example that will demonstrate the difference between the component wrapped into React.memo HOC and just plain component.
We have an "App" component that has 1 state variable "counter".
Also, it has 2 child components - PlainComponent (that is just a plain component that does not use React.memo HOC) and MemoComponent (that is wrapped into React.memo HOC)
function App() {
const [counter, setCounter] = useState(1);
return (
<div className="App">
<p> {counter}</p>
<button onClick={() => setCounter(counter + 1)}> Set Counter</button>
<div className="childComponents">
<MemoComponent />
<PlainComponent />
</div>
</div>
);
}
const PlainComponent = () => {
console.info("Child Component - no memo HOC");
return (
<div>
<h3> Plain Component </h3>
</div>
);
}
const MemoComponent = React.memo(() => {
console.info("Child Component - uses memo HOC");
return (
<div>
<h3> Memo Component </h3>
</div>
);
}
So, when we will change the state of the "App" component (by incrementing a counter), it should cause its child components to re-render.
But as you can see, only the plain component re-rendered.
React.memo & props
But we haven't passed any props to our child components.
What if we gonna pass the prop to the Memo component and this prop will change.
Is it going to ignore this change or it will re-render the component and reflect the modification?
Let's take a look at another example!
We are going to use the same "App" and "MemoComponent" but this time, I added one more state variable to the App component - "passedProp".
This variable will change every time the remainder of our "counter" will be equal to 0.
And we gonna pass this prop to the "MemoComponent"
function App() {
const [counter, setCounter] = useState(1);
const [passedProp, setPassedProp] = useState(0);
useEffect(() => {
if (counter % 5 === 0) setPassedProp(passedProp + 1);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [counter]);
return (
<div className="App">
<p> {counter}</p>
<button onClick={() => setCounter(counter + 1)}> Set Counter</button>
<div className="childComponents">
<MemoComponent prop={passedProp}/>
</div>
</div>
);
}
In MemoComponent we will just display passed prop
const MemoComponent = React.memo(({prop}) => {
console.info("Child Component - uses memo HOC");
return (
<div>
<h3> Memo Component </h3>
<p> {prop}</p>
</div>
);
}
Every time "passedProp" changes, our MemoComponent re-renders.
React.memo & state of the component
What if the component wrapped into React.memo HOC has its own state and this state changes?
Is it going to re-render or not?
Now, our MemoComponent has one state variable - "randomNumber" and the button to modify it.
const MemoComponent = React.memo(() => {
const [randomNumber, setRandomNumber] = useState(Math.random());
console.info("Child Component - uses memo HOC");
return (
<div>
<h3> Memo Component </h3>
<p> {randomNumber}</p>
<button onClick={() => setRandomNumber(Math.random())}>Set random</button>
</div>
);
});
Every time we change "randomNumber", our component gonna re-render.
So, if your component has a useState, useContext, or useReducer in its implementation, it will re-render when the state (context) changes.
When to use it ?
data-heavy components that are provided the same props all the time
big size component that has a decent amount of UI elements
Why not use it everywhere ?
Probably you thought about that.
But !!!
Internally React.memo compares props (their previous and new state) so it can decide whether to re-render component or not (if props changed - it should re-render, otherwise not)
And most of the time, computation for this comparison can be even more expensive and take even more time than just re-rendering the component
That is why you should not use React.memo if:
- component is cheap to re-render
- passed props change often (so there is no meaning to use memo, the component will re-render anyway)
- comparison function is expensive to perform
Also, you should not use it as a way to "prevent" a render.
It can lead to bugs!
And the last thing I want to mention is the custom comparison function that can be passed as the second argument.
This function can perform a comparison of previous and new props, and determine whether the component should re-render or not.
Why would we need this?
Consider this example:
In the "App" component we have an object that consists of 1 property and we pass this object to Memo Component.
We do not modify it anywhere.
function App() {
const [counter, setCounter] = useState(1);
const complexObject = useState({ qty: 0 });
return (
<div className="App">
<p> {counter}</p>
<button onClick={() => setCounter(counter + 1)}> Set Counter</button>
<div className="childComponents">
<MemoComponent prop={complexObject} />
</div>
</div>
);
}
const MemoComponent = React.memo(() => {
console.info("Child Component - uses memo HOC");
return (
<div>
<h3> Memo Component </h3>
</div>
);
});
But every time we gonna change the state by incrementing "counter", our MemoComponent is re-rendered (despite the fact we use React.memo)
Why is that happening?
When we change the state of the "App" component, we re-create an object, and React thinks that the passed prop has changed and thus forces MemoComponent to re-render.
So, how to fix it?
Easy-peasy.
We just have to pass the function as a second argument that will compare 2 states of props.
const MemoComponent = React.memo(
() => {
console.info("Child Component - uses memo HOC");
return (
<div>
<h3> Memo Component </h3>
</div>
);
},
(previousProps, nextProps) => {
return previousProps.prop.qty === nextProps.prop.qty;
}
);
So, as you can see we check whether the "qty" prop of a passed object has changed or not.
If the state of props is different, we have to return false, and this will cause a component to re-render.
Otherwise, the function returns true and we gonna use the previously rendered output.
And that's it, guys.
Now you are ready to use React.memo in your React projects!
I hope that you have learned something new today!
I would appreciate it if you could like this post or leave a comment below!
Also, feel free to follow me on GitHub and Medium!
Adios, mi amigos)
Top comments (0)