Memoization is the act of storing the result of an expensive function call, and re-using the result whenever itâs required, instead of re-calculating it unnecessarily. Itâs not something youâll need to use every day, but something to keep in mind if youâre looking for ways to improve the performance of your React app.
Finding problems using flame charts đ„
Before diving straight into adding memoization to your app, itâs good to diagnose where your appâs performance issues are coming from. One of the things you can do is to go over to your browserâs Performance tab and record your appâs performance as it renders.
What youâre seeing here is a flame chart in the Firefox browser (youâll see something similar in Chrome). Up the top is a complete timeline of what happened during your recording, and the darker blue bit is the section of the timeline that Iâve selected. All the colorful bars are whatâs happening in our app in that selected section of the timeline.
In this scenario Iâve pressed a button on my React app at about the 1250ms mark - and the framerate tanks (as you can see by the green line dropping at the top). We can see the root cause (at the bottom of the screen) is a calculatePrimes function happening inside of an Expensive
component.
This is what the code looks like:
const Expensive = ({ value }) => {
const primes = calculatePrimes(value, value) // does a lot of math
return <>{primes}</>
}
const App = () => {
const [count, updateCount] = useState(0)
return (
<>
<button onclick={updateCount(count + 1)}>Click</button>
<Expensive value={100} />
</>
);
}
By default, when a parent component re-renders, all of its children components will re-render too. So in this case, each time someone clicks the button (and changes the state of App
, causing it to re-render), Expensive
will also re-render and call calculatePrimes
.
Considering the fact that we are passing the same prop in to Expensive
each time, calculatePrimes
is going to return the exact same output each time. It doesnât really need to be re-rendered, and so there is an opportunity here to memoize the component.
Memoize components using React.memo()
We can make a component only re-render once its props have changed by wrapping it in React.memo
:
const ExpensiveMemo = memo(function Expensive({ value }) {
// ...
});
function App() {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>Click</button>
<ExpensiveMemo value={100} />
</>
);
}
Since ExpensiveMemo
's value
prop will always be 100
, it will render once when the page initially loads, and then never re-render again.
If youâre using class components, using React.PureComponent will achieve the same effect.
Watch out for your props!
In the above example, the prop weâre passing in is a number. But what if we passed in an object or an array?
<ExpensiveMemo value={['hello', 'world']}/>
Doing this will cause the Expensive
component to always re-render, as weâre creating a new array and passing it in each time.
On the other hand, if you modified an already-existing array and passed it in as a prop:
const countArray = [];
function App() {
//...
countArray.push[count]
return (
// ...
<ExpensiveMemo value={countArray}/>
That would never cause a re-render, as React memo
only does a shallow comparison on its props, which means that it only cares that countArray
is the same array (and not whatâs inside of it).
To fix this, you can pass in your own custom
areEqual
function as a second argument toReact.memo
.
Memoize values using React.useMemo()
Instead of worrying about whether weâre going to cause ExpensiveMemo
to re-render or not, alternatively we can just memoize the expensive calculatePrimes
function using the React.useMemo hook:
const Expensive = ({value}) => {
const primesMemo = useMemo(() => calculatePrimes(value, value), [value]);
return <>{primesMemo}</>
}
The first argument we pass into useMemo
is the function to calculate the value that we want. The second is an array of its dependencies (things that if they change, we will want to recalculate the value) which in our case is just the value
prop.
Now calculatePrimes
will only get called whenever value
changes!
If youâre using class components, there are libraries out there that accomplish the same thing as
useMemo
, such as memoize-one.
Conclusion
If your app is fairly small or doesnât have any expensive calculations, memoizing things in your code will add more code and complexity without actually providing any performance benefit so itâs not something Iâd advise using - but I think itâs definitely a good thing to keep in mind especially as your app grows bigger.
Thanks for reading!
Top comments (1)
I have a feeling memoization is gonna be the #1 trend in 2020.