DEV Community

Cover image for How to use the useMemo & useCallback React hook
Markus Persson
Markus Persson

Posted on

How to use the useMemo & useCallback React hook

Introduction

As your React skills grow, you'll start to care more and more about how well your app performs. When it comes to optimizing React applications, caching is as important as any other tool or programming technique. In React, caching is commonly referred to as memoization, and it is used to reduce the number of times a component is rendered due to state or prop changes. This article will explain how to use the useMemo and useCallback hooks in React for caching purposes.

The default caching in React

By default, React uses a technique called "shallow comparison" to decide whether a component should be re-rendered. It compares the previous props and state of the component with the new ones, and if there are no changes, React assumes that the component hasn't changed, and it won't re-render.

However, when dealing with more complex components that have advanced state management, this default mechanism may not be sufficient. In such cases, additional caching techniques are needed to optimize performance. That's where the useMemo and useCallback hooks come in.

useMemo hook

Note: useMemo hook can only return a value.

When a component renders, all the code within it is executed, including any functions defined within the component. If a function performs a complex calculation, it can be inefficient to execute it on every render, especially if the function's arguments hasn't changed.
React re-renders a component if a prop or state has changed.

This is where the useMemo hook comes into play. By wrapping the function in an useMemo hook, React will remember the calculated value from the function between renders. It will only call the function if any of useMemo's dependencies specified in the dependency array (the second argument of useMemo) has changed. If the dependencies remain the same from the previous render, the previously calculated value is returned, and bypassing the need for a Recalculation.

Example without useMemo:

Here you can see that {expensiveCalculation(count)} will re-render no matter if your click incrementCount or incrementCountRender becuase React re-renders a component if a prop or state has changed. Which results in everything inside of the JSX return() will get rendered again.

import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const [countRender, setCountRender] = useState(1)

  const incrementCount = () => {
    console.log('Increment count')
    setCount((c) => c + 1);
  };


  const incrementCountRender = () => {
    console.log('Increment count render')
    setCountRender((c) => c + 1)
  }

  return (
    <div>
      <div>
        Count: {count}
        <button onClick={incrementCount}>+</button>
        <br />
        Count render: {countRender}
        <button onClick={incrementCountRender}>+</button>
        <h2>Expensive Calculation</h2>
        {expensiveCalculation(count)}
      </div>
    </div>
  );
};

const expensiveCalculation = (num: number) => {
  console.log("Expensive calculation");
  for (let i = 0; i < 1000000000; i++) {
    num += 1;
  }
  return num;
};

export default App
Enter fullscreen mode Exit fullscreen mode

Example with useMemo:
I removed {expensiveCalculation(count)} from the JSX return() and added calculatedResult function which is wrapped with useMemo and returns expensiveCalculation(count) with a dependency array that has count inside of it. This useMemo function will only run in the initial render and if count has changed.

import React, { useMemo, useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const [countRender, setCountRender] = useState(1)

  const incrementCount = () => {
    console.log('Increment count')
    setCount((c) => c + 1);
  };


  const incrementCountRender = () => {
    console.log('Increment count render')
    setCountRender((c) => c + 1)
  }

  const calculatedResult = useMemo(() => {
    console.log('useMemo')
    return expensiveCalculation(count)
  }, [count])

  return (
    <div>
      <div>
        Count: {count}
        <button onClick={incrementCount}>+</button>
        <br />
        Count render: {countRender}
        <button onClick={incrementCountRender}>+</button>
        <h2>Expensive Calculation</h2>
        {calculatedResult}
      </div>
    </div>
  );
};

const expensiveCalculation = (num: number) => {
  console.log("Expensive calculation");
  for (let i = 0; i < 1000000000; i++) {
    num += 1;
  }
  return num;
};

export default App
Enter fullscreen mode Exit fullscreen mode

useCallback hook

Note: useCallback hook returns a function.

The useCallback hook in React allows you to memoize a function, ensuring that the function reference remains the same between renders unless any dependencies specified in the dependency array (the second argument of useCallback) have changed.

When a component renders, all the code within it is executed, including the creation of any functions inside the component. If a function is passed down as a prop to child components, it can lead to unnecessary re-renders of those child components if the function reference changes, even if the logic inside the function hasn't changed.

Example without useCallback

The getTodos function retrieves a single todo from the todos array based on the count state. This function is then passed as a prop to the TodosList component, where it is used in an useEffect hook to update the state by spreading the retrieved todo with the existing todos.

This functionality works correctly when there is no countTwo state or setCountTwo button, as they trigger a re-render of the component. If the button is clicked, it will cause the TodosList component to re-render, resulting in the addition of an old todo to the todos state, which can lead to an error.

import React, { useEffect, useState } from "react";

const todos = [
  {id: 1, title: 'Todo 1'},
  {id: 2, title: 'Todo 2'},
  {id: 3, title: 'Todo 3'},
  {id: 4, title: 'Todo 4'},
  {id: 5, title: 'Todo 5'},
]

const App = () => {
  const [count, setCount] = useState(0);
  const [countTwo, setCountTwo] = useState(100)

  const getTodos = () => {
    return todos[count]
  }

  return (
    <div>
      <div>
        Count: {count}
        <button onClick={() => setCount(count + 1)}>+</button>
        <br />
        Count Two: {countTwo}
        <button onClick={() => setCountTwo(countTwo - 1)}>+</button>
        <br />
        <TodosList getTodos={getTodos}/>
      </div>
    </div>
  );
};

const TodosList = ({getTodos}) => {
  const [todos, setTodos] = useState([])

  useEffect(() => {
    console.log('getTodos function is called.')
    setTodos([...todos, getTodos()])
  }, [getTodos])

  return (
    <>
    {todos.map(todo => <span key={todo.id}>{todo.title} </span>)}
    </>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Example with useCallback:

Here, we have wrapped the getTodos function in a useCallback hook. This ensures that the function will only be executed during the initial render of the component or when the count state has changed. Otherwise, it won't be invoked.

import React, { useCallback, useEffect, useState } from "react";

const todos = [
  {id: 1, title: 'Todo 1'},
  {id: 2, title: 'Todo 2'},
  {id: 3, title: 'Todo 3'},
  {id: 4, title: 'Todo 4'},
  {id: 5, title: 'Todo 5'},
]

const App = () => {
  const [count, setCount] = useState(0);
  const [countTwo, setCountTwo] = useState(100)

  const getTodos = useCallback(() => {
    return todos[count]
  }, [count])

  return (
    <div>
      <div>
        Count: {count}
        <button onClick={() => setCount(count + 1)}>+</button>
        <br />
        Count Two: {countTwo}
        <button onClick={() => setCountTwo(countTwo - 1)}>+</button>
        <br />
        <TodosList getTodos={getTodos}/>
      </div>
    </div>
  );
};

const TodosList = ({getTodos}) => {
  const [todos, setTodos] = useState([])

  useEffect(() => {
    console.log('getTodos function is called.')
    setTodos([...todos, getTodos()])
  }, [getTodos])

  return (
    <>
    {todos.map(todo => <span key={todo.id}>{todo.title} </span>)}
    </>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Conclution

useMemo: allows you to calculate and store a value only when its dependencies have changed. By memoizing the value, Then React can avoid unnecessary recalculations and re-renders of the component, improving overall performance.

useCallback: is used to memoize a function. It returns a memoized version of the function that will only be called if its dependencies has been changed. This is particularly useful when passing callbacks as props to child components.

By using useMemo and useCallback wisely, you can optimize your React application by reducing unnecessary calculations and rendering, leading to improved performance and a smoother user experience.

Top comments (2)

Collapse
 
mamunahmed profile image
Mamun Ahmed

Very nice explanation!

Collapse
 
markusmp profile image
Markus Persson

Thanks!