React - to memoize or not to memoize, that is the question!

I often get asked about memoization in React during tech firm interviews: What are the performance benefits of memoizing a component? How can I measure these gains, if any? And when does it make sense to use or avoid memoization? Finally, decided to do a short write-up on it. You can find the reference to the official docs here.
How does React rendering work?
As you may know, one of the core features of the React framework is its Virtual DOM, which enables efficient UI rendering. The goal is to re-render a component only when a change is detected.
Let’s start with a simple example to demonstrate how rendering works in a component that contains child components.
We will define 3 components:App.jsx
, ParentComponent.jsx
and ChildComponent.jsx
import { useState, useEffect } from "react";
import ParentComponent from "./components/ParentComponent";
import './App.css';
function App() {
useEffect(() => {
console.log("Inside useEffect of component: App");
}, []);
return (
<ParentComponent>
{console.log("Inside App.js HTML")}
</ParentComponent>
)
}
export default App
import {useEffect, useState} from "react";
import ChildComponent from "../ChildComponent/";
import "./ParentComponent.css";
const ParentComponent = ({}) => {
useEffect(() => {
console.log("Inside useEffect of component: ParentComponent");
}, []);
return (
<div className={"parent"} id={"parent-component"}>
{console.log("Inside Parent component HTML")}
<ChildComponent />
</div>
);
}
export default ParentComponent;
import React, { useEffect, useState } from 'react';
import "./ChildComponent.css";
const ChildComponent = ({}) => {
useEffect(() => {
console.log("Inside useEffect of component: ChildComponent");
}, []);
return (
<div className={"child"} id={"child-component"}>
{console.log("Inside child component HTML")}
</div>
);
}
export default ChildComponent;
If you run the above code in the browser, the console log should show as follows:
Inside App.js HTML
Inside App.js HTML
Inside Parent component HTML
Inside Parent component HTML
Inside child component HTML
Inside child component HTML
Inside useEffect of component: ChildComponent
Inside useEffect of component: ParentComponent
Inside useEffect of component: App
Inside useEffect of component: ChildComponent
Inside useEffect of component: ParentComponent
Inside useEffect of component: App
Notice the order - HTML code is rendered first from top to bottom and useEffect
is executed from bottom to top.
Now, if we modify ParentComponent
to have a state variable to force re-render, the ChildComponent
also is re-rendered even though it has no dependency on the ParentComponent
updates. Let’s update the code to check that flow now.
Updates to ParentComponent.jsx
import {useEffect, useState} from "react";
import ChildComponent from "../ChildComponent/";
import "./ParentComponent.css";
const ParentComponent = ({}) => {
const [currCount, setCurrCount] = useState(0);
useEffect(() => {
console.log("Inside useEffect of component: ParentComponent");
const intervalId = setInterval(() => {
setCurrCount(prevCount => prevCount + 1);
}, 5000);
return () => {clearInterval(intervalId)};
}, []);
return (
<div className={"parent"} id={"parent-component"}>
{console.log("Inside Parent component HTML")}
<ChildComponent testProp={"testProp"} />
</div>
);
}
export default ParentComponent;
import React, { useEffect } from 'react';
import "./ChildComponent.css";
const ChildComponent = ({testProp}) => {
useEffect(() => {
console.log("Inside useEffect of component: ChildComponent");
}, []);
useEffect(() => {
console.log("Inside useEffect - 2 of component: ChildComponent");
}, [testProp]);
return (
<div className={"child"} id={"child-component"}>
{console.log("Inside child component HTML")}
</div>
);
}
export default ChildComponent;
On running the application, the console output looks like this:
Inside App.js HTML
Inside App.js HTML
Inside Parent component HTML
Inside Parent component HTML
Inside child component HTML
Inside child component HTML
Inside useEffect of component: ChildComponent
Inside useEffect - 2 of component: ChildComponent
Inside useEffect of component: ParentComponent
Inside useEffect of component: App
Inside useEffect of component: ChildComponent
Inside useEffect - 2 of component: ChildComponent
Inside useEffect of component: ParentComponent
Inside useEffect of component: App
Inside Parent component HTML
Inside Parent component HTML
Inside child component HTML
Inside child component HTML
......
As you can notice now, every update to a state variable in ParentComponent
causes an additional render on ChildComponent
even though no props are updated.
This is where memoization comes into the picture. Let’s memoize the ChildComponent
now to only re-render when a property has changed from ParentComponent
.
Create a memoized ChildComponent
import React, { useEffect } from 'react';
import "./ChildComponent.css";
const ChildComponent = ({testProp}) => {
useEffect(() => {
console.log("Inside useEffect of component: ChildComponent");
}, []);
useEffect(() => {
console.log("Inside useEffect - 2 of component: ChildComponent");
}, [testProp]);
return (
<div className={"child"} id={"child-component"}>
{console.log("Inside child component HTML")}
</div>
);
}
const MemoizedChildComponent = React.memo(ChildComponent)
export default MemoizedChildComponent;
Rerun the project to see the console output:
Inside App.js HTML
Inside App.js HTML
Inside Parent component HTML
Inside Parent component HTML
Inside child component HTML
Inside child component HTML
Inside useEffect of component: ChildComponent
Inside useEffect - 2 of component: ChildComponent
Inside useEffect of component: ParentComponent
Inside useEffect of component: App
Inside useEffect of component: ChildComponent
Inside useEffect - 2 of component: ChildComponent
Inside useEffect of component: ParentComponent
Inside useEffect of component: App
Inside Parent component HTML
Inside Parent component HTML
Inside Parent component HTML
Inside Parent component HTML
Inside Parent component HTML
Inside Parent component HTML
......
As you notice now, the console line Inside child component HTML
is no longer to be seen since the memoized child component restricts the redundant re-rendering. To ensure that re-rendering works when a state variable in the parent component changes, pass the variable currCount
variable to the child component as a prop. I will leave it to you to make that change and see the code work :-).
What are the benefits of making ChildComponent
memoized?
React DevTools is a great way to analyze what’s going on with one’s React application. Here is what I found after running this simple React application.
This is the performance without ChildComponent
being memoized.
As you can see, every update in a state variable of ParentComponent
also re-rendered ChildComponent
thereby adding 0.6ms in time to completion. This might be small but look at it in the context of how much time it took ParentComponent
to complete in the screenshot below.
As you can see, the ParentComponent
subtree took 2.1ms to complete and the component itself took 1.1ms out of it. So we delayed the completion of ParentComponent
by at least 50% more time due to the redundant rendering of the child components.
Now let’s see the performance once we have memoized the ChildComponent
.
As you can see, the 2nd render doesn’t display the ChildComponent
hence allowing ParentComponent
to complete rendering in 1.7ms vs 2.1ms before which is approx. 20% reduction in time. Hence, memoization definitely helps in speeding up the render time if we figure out the components that are being re-rendered unnecessarily. This brings me to the next and last section of this article.
When does memoization help in React?
My rule of thumb is - measure first, optimize later.
The following scenarios usually indicate that memoization would be helpful:
The component needs complex or periodic calculations. For e.g. a component that needs to calculate the time left to reach the destination should only re-render if the distance to reach destination has changed.
The component that frequently received the same props. For e.g. a weather screen since the temperature is likely to change over a longer period.
However, one should also know when it has little to no benefits:
When a child component is not going to be updated due to any change from the parent component. E.g. static components which are part of a parent component.
When child component
props
change frequently, memoization doesn’t help since the re-rendering is unavoidable.When child component is lightweight and cheap to render. E.g. a component with few text fields won’t add to your degraded performance.
Wrap Up
I hope this article gave you insights into how to use memoization and when it is likely to reap more benefits while writing your React code. Please do comment if you have any feedback on the article or have something interesting to share. Signing off from my end!
Subscribe to my newsletter
Read articles from Shyam Katti directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
