The useMemo()
hook memoizes values returned by expensive functions. But what if we have an expensive React component that is re-rendered even though its props do not change?
When React detects a change in a parent component, it will re-render all of its child components to make sure the app is up to date. This may create a performance issue when a child component renders something expensive, like thousands of elements or an iframe.
To solve this problem, React provides a higher-order component named React.memo()
. A higher-order component is a component that takes another component as an argument so that it can add functionality to it. In this case, React.memo()
will only allow the component passed to it to re-render if its props have changed. Here’s what the syntax of React.memo()
looks like:
import React from 'react'; const MemoizedListComponent = React.memo((props) => { const { longList } = props; return longList.map(item => <li>{item}</li>); });
In the code above, we pass a function component to React.memo()
as its first argument. React.memo()
will compare the props
of the function component before and after each render phase and only re-render it if the values of those props
have changed. Then, we assign the result of React.memo()
to MemoizedListComponent
, which we can use in other components.
Note that React will only shallowly compare the props of the memoized component before and after each render. To ensure that complex values such as objects and arrays are deeply compared, we can provide a comparison function as the second argument to React.memo()
.
const areDeeplyEqual = (previousProps, nextProps) => { return JSON.stringify(previousProps) === JSON.stringify(nextProps) }; const MemoizedComponent = React.memo(ExpensiveComponent, areDeeplyEqual);
By applying React.memo()
, the function component will not re-render if its parent changes and its props stay the same. Depending on the rendering performance of the child component, this can result in a substantial performance benefit.
Instructions
Task 1
Go to the project’s home page (‘/‘), and then click on “Exercise 3: Memoizing Components”.
Task 2
This exercise presents a randomized GitHub contribution graph with 10,000 graph points. Like the last exercise, our graph also has a button to show an explainer of the graph. Open the React profiler dev tool, click the cog icon to open its settings, and toggle on “Highlight updates when components render.”. With the React profiler open, click on the “Show Explainers” button and notice that each graph point is highlighted.
Task 3
Let’s dive into the code to see what’s happening. Open ./src/pages/Exercise3/index.js
. In this component, we have React the state value showExplainer
, and then a .map()
to create 10,000 graph points with <GraphPoint />
. Since showExplainer
does not affect <GraphPoint>
, why does updating showExplainer
cause each <GraphPoint />
to re-render?
Hint
In React, whenever a component’s state updates, React will update that component and all of its child components. In this case, updating the state of showExplainer
changes <Exercise3 />
. Since <Exercise3 />
renders many <GraphPoint />
components as its children, they will also be re-rendered.
Task 4
Open ./src/pages/Exercise3/GraphPoint.js
, and let’s apply React.memo()
by importing React
from 'react'
, and then passing the GraphPoint
component to React.memo()
. Finally, make sure to export the memoized component as a variable named GraphPoint
.
Hint
We can apply React.memo()
with code like this:
import React from 'react'; export const MemoizedComponent = React.memo((props) => { // ... })
If you’re stuck, you can find the solution code in ./src/pages/Exercise3/solution/GraphPoint.js
.
Task 5
Reload the page and with the React profiler open, click the “Show Explainers” button on and off, and notice that each <GraphPoint />
no longer is highlighted, which signifies that they are no longer re-rendered when showing the explanation dialog.