Back

Understanding the useMemo and useCallback hook in React

A guide that explains how to use useMemo and useCallback hook when to used it and not to used it.


a year ago
4 min read

What is React Hooks

Hooks were added to React in version 16.8.

Hooks allow function components to have access to state and other React features. Because of this, class components are generally no longer needed.

Hooks make your work a lot easier and also to avoid a repetitive code over and over again.

We're not going to discuss all react hooks but we're going to understand what useMemo and useCallback hooks and when to used it.

The useMemo Hook

The useMemo hook is used to memoize a value that is expensive to compute. This means that the value is only computed when one of its dependencies changes. This can be useful for optimizing the performance of a component that needs to perform a heavy computation each time it renders.

We really have to understand useMemo hook and how it works and when to used it because if you don't use it when you should, it will trigger a performance problem and might have some bugs some unexpected behavior.

First, let talk about the problem of not using a useMemo.

Take a look at this code and analyze it.

import { useState } from "react"
 
const initialItems = new Array(29_999_999)
  .fill(0)
  .map((_, index) => ({ id: index, isSelected: index === 29_999_999 }))
 
const Component = () => {
  const [count, setCount] = useState(0)
  const [items] = useState(initialItems)
 
  const selectedItem = item.find((item) => item.isSelected)
 
  return (
    <div>
      <h1>Count: {count}</h1>
      <h2>Selected Item: {selectedItem?.id}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

As you can see above of the code we import useState from react and create a initialItems with value of (29_999_999) times and array elements in fill of 0 then we map it then return of object value of id and isSelected. isSelected is a boolean value only will it true when the index of array is equal to length of 29_999_999.

It looks like a normal nothing wrong declare useState pass initialItems as initialState and count and setCount with value of 0 but if you run this code in react application it will cause a problem, try it in your application and try to spam by clicking the increment button the Count: count is skipping number.

This is wrong and i'ts not a performant application should look like, so what is the problem?.

We need to look at our components and understand how react works under the hood and how react treats state updates and how it renders the components.

In our components we have a count, items and selectedItem if we click the button all we're doing is we are calling the function in onClick and increment the count.

We need to understand that to update state means you have to trigger a re-render of the entire components.

The problem is that the selectedItem triggering our performance issues if you look of what it does takes the item the goes in the array of items to find the item isSelected but if we take a look at initialItems the only thing that make the isSelected value is that to has go throught 29 millions item before isSelected value make true then will return that value and this is very expensive operations.

Because we are changing the count we are triggering the components to re-render and causing our selectedItem to recalculated every time the count changes. It causing a huge performance issue we're doing some unnecessery computations on every render.

This is will useMemo comes to play.

Do we really need to useMemo on items if the items still the same No? because it still the same no matter what happen but if we take a look at our component selectedItem there's no way the selectedItem still the same.

import { useState, useMemo } from "react"
 
const initialItems = new Array(29_999_999)
  .fill(0)
  .map((_, index) => ({ id: index, isSelected: index === 29_999_999 }))
 
const Component = () => {
  const [count, setCount] = useState(0)
  const [items] = useState(initialItems)
 
  const selectedItem = useMemo(
    item.find((item) => item.isSelected),
    [item]
  )
 
  return (
    <div>
      <h1>Count: {count}</h1>
      <h2>Selected Item: {selectedItem?.id}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

If the items in dependencies array in useMemo the selectedItem will re-renders.

Now go back to our applications take a look and try to spam the increment button you will see the difference of the two when we're not using useMemo hook. No lag, skipping numbers and it's super efficient.

The useCallback Hook