DEV Community

Paul-Simon Emechebe
Paul-Simon Emechebe

Posted on

Supercharging your React projects with Memoization

useMemo and useCallback are React hooks for memoization. Think of memoization as caching a value so that it does not need to be recalculated. This improves performance. The main difference between useMemo and useCallback is that useCallback returns a memoized function (which just really means a 'cached function') and useMemo returns a memoized value. Let's go through these hooks together.

Let's start with useMemo

Using useMemo

One reason to use useMemo is to prevent an expensive function from re-rendering unless one of its dependencies update.

Problem

In this example, we have an expensive function that runs on every render.

When changing the count or adding a todo, you will notice a delay in execution.

import { useState } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);
  const calculation = expensiveCalculation(count);

  const increment = () => {
    setCount((c) => c + 1);
  };
  const addTodo = () => {
    setTodos((t) => [...t, "New Todo"]);
  };

  return (
    <div>
      <div>
        <h2>My Todos</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>;
        })}
        <button onClick={addTodo}>Add Todo</button>
      </div>
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
        <h2>Expensive Calculation</h2>
        {calculation}
      </div>
    </div>
  );
};

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

ReactDOM.render(<App />, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

Solution

To fix this performance issue, we have to find a way to prevent the rerender of the expensive function. To do this we'll have to memoize the expensive function. This is done by wrapping the expensive function call with useMemo.

The useMemo Hook accepts a second parameter to declare dependencies. The expensive function will only run when its dependencies have changed.

In the following example, the expensive function will only run when count is changed and not when todo's are added.

import { useState, useMemo } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);
  const calculation = useMemo(() => expensiveCalculation(count), [count]);

  const increment = () => {
    setCount((c) => c + 1);
  };
  const addTodo = () => {
    setTodos((t) => [...t, "New Todo"]);
  };

  return (
    <div>
      <div>
        <h2>My Todos</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>;
        })}
        <button onClick={addTodo}>Add Todo</button>
      </div>
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
        <h2>Expensive Calculation</h2>
        {calculation}
      </div>
    </div>
  );
};

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

ReactDOM.render(<App />, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

Let's look at useCallback!!!

Using useCallback

The main difference between useMemo and useCallback is that useCallback returns a memoized function (which just really means a 'cached function') and useMemo returns a memoized value.

( for the code example for useCallback, we'll use the exact example used for the useMemo but the expensive function would be in another component called ExpensiveCalc.js.)


import { useState } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  const increment = () => {
    setCount((c) => c + 1);
  };

  const expensiveCalculation = useCallback((num) => {
  console.log("Calculating...");
  for (let i = 0; i < 1000000000; i++) {
    num += 1;
  }
  return num;
  }, [count]);  

  return (
    <div>
      <div>
        <h2>My Todos</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>;
        })}
        <button onClick={addTodo}>Add Todo</button>
      </div>
      <hr />
      <div>
        <ExpensiveCalc count={count} increment={increment}/>
      </div>
    </div>
  );
};




ReactDOM.render(<App />, document.getElementById('root'));


Enter fullscreen mode Exit fullscreen mode

ExpensiveCalc.js

import { memo } from "react";

const ExpensiveCalc = ({ count, increment }) => {
  console.log("child render");
  return (
    <>
      <h2>Count</h2>
      Count: {count}
      <button onClick={increment}>+</button>
    </>
  );
};

export default memo(ExpensiveCalc); //memo will cause React to skip rendering a component if its props have not changed.
Enter fullscreen mode Exit fullscreen mode

Discussion (2)

Collapse
link2twenty profile image
Andrew Bone

Hello!

If you'd like, you can add syntax highlighting (colors that make code easier to read) to your code block like the following example.

console.log('Hello world!');
Enter fullscreen mode Exit fullscreen mode

This should make it a bit easier to understand your code. 😎

In order to use this in your code blocks, you must designate the coding language you are using directly after the first three back-ticks. So in the example above, you'd write three back-ticks js (no space between the two), put the code beneath (e.g. console.log('Hello world!');), then three back-ticks beneath that to close the code.

Here's an image that shows how it's written!

Example of how to add syntax highlighting in code blocks

Collapse
paul_emechebe profile image
Paul-Simon Emechebe Author

thanks so much, was actually looking on how to highlight the code snippets.