Left Arrow.Back

memo, useMemo & useCallback

A number of circles connected by lines. Some flashing with color.

When & how to use memo, useMemo & useCallback in React.

Last Tended-
PlantedSep 2021
Statusseed
TagsReact
The word 'prologue' in serif font with a line underneath.

Prologue

Before we can talk about React's built-in hooks, we need to cover 2 concepts:

  • Referential Equality
  • Memoization
A triangle balancing a straight line on top of it.

Referential Equality

In JavaScript, if you compare primitive types (string, number, boolean) using the equality operator (===), the results is what you would except:

/index.js

const bool1 = true
const bool2 = false
bool1 === bool1 // result: true
bool1 === bool2 // result: false
const num1 = 1
const num2 = 2
num1 === num1 // result: true
num1 === num2 // result: false
const letter1 = `a`
const letter2 = `b`
letter1 === letter1 // result: true
letter1 === letter2 // result: false

However, if you compare structural types (object, array, function), the result isn't as intuitive:

/index.js

const person1 = {
name: `Deckard`
}
const person2 = {
name: `Deckard`
}
person1 === person2 // result: false
const letters1 = [`a`, `b`]
const letters2 = [`a`, `b`]
letters1 === letters2 // result: false
const function1 = () => null
const function2 = () => null
function1 === function2 // result: false

Take a look at person1 & person2. They are both the same type (object) & have the same properties & values. It would makes sense if the equality operator returned true when comparing them, but it doesn't. This is because when you compare structural types, the operator is testing reference equality, not value equality. Testing whether they are the same instance, not whether they are the same value. person1 & person2 may have the same value, but they are 2 different object instances (if I change a property on 1, the other will remain the same). Thus, when compared using the equality operator, it returns false.

Multiple circles (hollow on the left, fill on the right). A line connecting them with a square in the middle.

Memoization

In computer science, memoization is an optimization technique. You can take any regular function and memoise it. When memoised, the 1st time the function is called, it performs its calculation and obtains the return value. But before returning that value, it writes down what arguments it was passed, the value it is returning & stores them in a cache.

The next time the function is called, it checks the cache to see if it has seen the new arguments before. If so, it won't perform its calculation, it will just return the value it has written down in the cache. If not, it will perform its calculation & add a new record to the cache. This way, the function never performs its calculation on the same inputs twice. Reducing CPU time (at a cost of more memory). This technique would be used it there was a computationally expensive function that was taking a long time to complete & impacting UX (User Experience).

Without Memoization

Below is an example of a function that takes a number, multiples it by 2, then returns the result. It also logs everytime it performs the calculation. We are calling this function 3 times. The 3rd call has the same argument as the 1st. You can see in the console that the funciton is calculating each time it is called.

/index.js

Console

With Memoization

Let memoise the function. We can add a log object that our function will check everytime its called. If it finds a value for the input in the log, it will return that without needing to perform the calculation. If it doesn't, it performs the calculation, adds the result to the log, then returns the value. You can see in the console, the 3rd time we called the function (with the same input as the 1st call), no calculation was logged. Instead, the function just returned the value from the log.

/index.js

Console

Multiple cogs.

Built-in hooks

Referential equality & computationally expensive calculations (that could be optimized by memoization) is why memo, useMemo & useCallback are built into React. This is because of things like structural types (object, array, function) defined inside a React component will be a different instance each time the component renders.

A computer screen with a blinking cursor.

Example: memo

Non-Optimized

Imagine we had 2 React components, MyParent & MyChild. We render MyChild in MyParent. The parent also has a useState hook with a boolean value. We'll then get the parent to render a button that, when clicked, toggles the state from true to false & vice versa.

/MyParent.tsx

https://localhost:3000

The toggle button is there just to trigger a re-render of the parent. When the hook's state changes, the component with the hook will re-render. You will notice, however, that the child is also re-rendering (even though no changing props are being passed to it). This is to prove 1 thing, that when a parent re-renders, so does its children.

Optimized

This child's re-rendering is what we call unnecessary re-renders. When the parent is re-rendering, nothing is changing in the child component. We can prevent this re-rendering by using memo. If a component is wrapped by memo & it is about to re-render, a check will be made. Memo will look at the props being passed to the component. If there is no change between the props before this render call to the current props, the component will not re-render.

/MyParent.tsx

https://localhost:3000

A computer screen with a blinking cursor.

Example: useCallback

Non Optimized

Imagine we made a DualCounter component. A component that has 2 buttons. Each button keeps track of how many times it has been clicked. When a button is clicked, the state in 1 of the hooks changes. This triggers a re-render on the parent, which in turn, triggers a re-render of its children.

/DualCounter.tsx

https://localhost:3000

When we click button 1, its counter increases by 1. So it makes sense that the parent & it re-render. The other button however, whose counter doesn't change, does an unnecessary re-render. Non of its props change. So, we can use memo, like before, to prevent this.

/DualCounter.tsx

https://localhost:3000

Hmm... that didn't seem to work. When button 1 is clicked, button 2 is still re-rendering. Memo is supposed to prevent re-renders on a component if its props don't change. Ahh, but they are. The count prop is a primitive type, its not changing. However, the onClick property is a function, a structural type. When the parent re-renders, the function is replaced by a new instance. Because of referential equality check, the old onClick doesn't equal the new onClick. Thus, button 2 re-renders.

Optimized

This is where we can use the useCallback hook. useCallback will return a memoized version of the callback that only changes if 1 of the dependencies changes. Wrapping increment1 in this hook will maintain the instance of this function when the parent re-renders (unless 'setCount1' changes). Which means, when memo looks at the onClick prop, it will compute 'oldOnClick1 === newOnClick1'. This will return true, & thus the component will not re-render.

/DualCounter.tsx

https://localhost:3000

A computer screen with a blinking cursor.

Example: useMemo

Not Optimized

Imagine we had a component that needed to do a computationally expensive calculation on some data before rendering it. Below is a component that has an 'expensive function'. A function that takes a long time to complete and is slowing down our app. The function takes an input value and transform it. In this example, where using a hook to control this value, but in reality it would be likely to come from a prop passed to this component. Each time the input changes, the expensive function needs to re-run. Which is normal. However, each time the component re-renders and the input doesn't change, the function re-runs.

MyComponent.tsx

https://localhost:3000

Optimized

We can reduce the amount of times this function is called by using useMemo. useMemo returns a memoized value. It will noly recompute the value if 1 of the dependencies changes. By wrapping the call to expensiveFunction with useMemo, where preventing it from re-running unless 'input' changes'. When the component re-renders due to 'isTrue' changing, the call to expensive function isn't executed. Thus, speeding up our app.

MyComponent.tsx

https://localhost:3000

At the top of this note we went through memoization and how a memoised function remembers all previous inputs & outputs. It never needs to perform a calculation on the same input value twice. useMemo isn't that sophiscated. It you pass it a value, then change that value, then change the value back to the original, it will do a calculation on the original value again.

A triangle with an exclamation mark in it.

Warning

You should not use memo, useMemo or useCallback unless you are noticing a performance problem (such as a component rendering slow). Performance optimisations aren't free. For example, when implementing useMemo, you free up CPU time but you pay by taking up more memory. If you need a reference to an object or array that doesn't require recalculation, useRef could be a better choice.