How to use React memoization hooks for increased performance
Tags: react, typescript, javascript, tutorial
Published: 2022-08-11 09:20:00

As React apps grow bigger and more complex, performance becomes more and more of a problem. As components become larger and contain more and more sub-components, rendering becomes slow and turns into a bottleneck.

How do we tackle this? If you haven't used `useMemo` and `useCallback`, we can start with those.

<!-- more -->

In this tutorial, we will take a look at how these 2 very easy and handy callbacks work, and why they are so useful. In fact, these days my eyes get sore when I don't see them used. So let's dive in to what they do.

<hr class="center" />

## React.useMemo

This React hook's one goal is to save a value for later use, and not re-calculating it on the spot.

Let's take an example of some expensive logic that runs in our render function:

```tsx
const ExpensiveComponent: React.FC = (props) => {
  const [list, setList] = React.useState([])
  const [counter, setCounter] = React.useState(0)
  const multiplied = list.map((i) => (i * 972 + 1000) / 5213).join(', ')

  function addRandom() {
    setList((prev) => [...prev, Math.floor(Math.random() * 10000)])
  }

  function increaseCounter() {
    setCounter((prev) => ++prev)
  }

  return (
    <div>
      Counter: {counter}
      <br />
      Multiplied: {multiplied}
      <br />
      <button onClick={addRandom}>Add Random</button>
      <button onClick={increaseCounter}>Increase Counter</button>
    </div>
  )
}
```

Doesn't seem very problematic, but take a look at the `multiplied` variable. The logic in this example isn't too bad, but imagine working with a giant list of special objects. This mapping alone could be a performance problem, especially if it's looped in a parent component.

In this case, there is another state hook - `counter`. When `setCounter` is called, `multiplied` will be calculated all over again, wasting previous resources, even when an update in that case is not needed as these variables are independent of each-other.

That's where `useMemo` comes in hand (read the [official docs](https://reactjs.org/docs/hooks-reference.html#usememo)).

You can use this hook to save the value, and retrieve the same object until a re-calculation is needed.

Here's how it's used, the only line we need to change is the `multiplied` definition:

```tsx
const multiplied = React.useMemo(() => {
  console.log('recalculating multiplied:', list)
  return list.map((i) => (i * 972 + 1000) / 5213).join(', ')
}, [list])
```

The `useMemo` hook takes 2 arguments:

1. The `create` function - used to return the calculated value of the variable we want to eventually use
2. A list of dependencies. The dependency list is used to determine **when** a new value should be calculated - i.e, when to run the `create` function again.

We added a `console.log` call here just to note when a new value is being calculated.

And with those changes, we can try our component again (here is the updated code just in case):

```tsx
const ExpensiveComponent: React.FC = (props) => {
  const [list, setList] = React.useState([])
  const [counter, setCounter] = React.useState(0)
  const multiplied = React.useMemo(() => {
    console.log('recalculating multiplied:', list)
    return list.map((i) => (i * 972 + 1000) / 5213).join(', ')
  }, [list])

  function addRandom() {
    setList((prev) => [...prev, Math.floor(Math.random() * 10000)])
  }

  function increaseCounter() {
    setCounter((prev) => ++prev)
  }

  return (
    <div>
      Counter: {counter}
      <br />
      Multiplied: {multiplied}
      <br />
      <button onClick={addRandom}>Add Random</button>
      <button onClick={increaseCounter}>Increase Counter</button>
    </div>
  )
}
```

If you now change the counter by using the "Increase Counter" button, you will see our `console.log` call is not being invoked again until we use the other button for "Add Random".

## React.useCallback

Now we have the other hook - `useCallback` (read the [official docs](https://reactjs.org/docs/hooks-reference.html#usecallback)).

This works exactly like the `useMemo` hook - except it is for functions instead of variable values.

We can take our button functions, and wrap each in this hook to make sure our function reference only changes when needed.

```tsx
const ExpensiveComponent: React.FC = (props) => {
  const [list, setList] = React.useState([])
  const [counter, setCounter] = React.useState(0)
  const multiplied = React.useMemo(
    () => list.map((i) => (i * 972 + 1000) / 5213).join(', '),
    [list],
  )
  const addRandom = React.useCallback(
    () => setList((prev) => [...prev, Math.floor(Math.random() * 10000)]),
    [setList],
  )
  const increaseCounter = React.useCallback(() => setCounter((prev) => ++prev), [setCounter])

  return (
    <div>
      Counter: {counter}
      <br />
      Multiplied: {multiplied}
      <br />
      <button onClick={addRandom}>Add Random</button>
      <button onClick={increaseCounter}>Increase Counter</button>
    </div>
  )
}
```

Now both our variables and functions are memoized and will only change reference when their dependencies dictate they do.

## Caveats

Using these hooks don't come without their share of problems.

1. Consider whether this actually improves performance or not in your specific case. If your state is pretty regularly changed and these memoizations have to run pretty often, their performance increases might be outweighed by the performance costs of actually running the memoization logic.

2. Dependency checking and generating can be expensive. Be careful what you put in the dependency lists, and if needed, make some more memoization and map your objects and lists in deterministic ways so that they are statically inspectable easily. Also avoid using expensive methods such as `JSON.stringify` to create those memoizations or dependencies, as it might also be too expensive to be worth the trouble and might make things worse.

## Other things to consider

You might want to make sure your project uses lint rules that enforce exhaustive dependencies, as they make tracking these things much more easy.

In some cases, you might want to add ignore comments in very specific places, but it makes it very clear that this part is built that way intentionally and prevents more confusion about when to update the dependencies.

---

Hopefully you find this useful. There are many other hooks to learn about, but these 2 are very useful and often ignored, so I thought it would be good to highlight them. If you are interested you might want to look up `useRef` and how it differs from `useMemo`, or maybe I'll make another part about it in the future. Who knows?