🦸‍♀️React useMemo Hook ultimate guide🚀

🦸‍♀️React useMemo Hook ultimate guide🚀

·

7 min read

🎯 Introduction

Hello, Developers!! 😇👩‍💻 With the release of React 16.8, there are many useful hooks you can now use in your React applications. One of the built-in Hooks that was introduced in 16.8 is useMemo. This hook has the potential to improve performance in your application.

This article will explore how re-rendering works in React, why that is an important consideration for React applications, and how the useMemo hook can leverage it to create a performance boost in your applications. it provides to make your code even faster.

Austin Powers Hello GIF

🔑Definition:-

useMemoreturns a memoized value, which helps avoiding expensive calculations on every render.

What problem does useMemo solve?🧐

Seems Jimmy Fallon GIF by The Tonight Show Starring Jimmy Fallon

useMemo is a React hook that memorizes the output of a function. That is it. useMemo accepts two arguments: a function and a list of dependencies. useMemo will call the function and return its return value. Then, every time you call useMemo again, it will first check if any dependencies have changed. If not, it will return the cached return value, not calling the function. If they have changed, useMemo will call the provided function again and repeat the process.

import { useMemo } from 'react';
// ...
const result = useMemo(() => runHeavyCalc(data), [data]);

I will explain this as a point so you can easily remember this.

You need to pass two things to useMemo:

  1. A calculation function that takes no arguments, like () =>, and returns what you wanted to calculate.

  2. A list of dependencies including every value within your component that’s used inside your calculation.

When should you use it?🤷‍♀️

Look At Us Paul Rudd GIF by First We Feast: Hot Ones

Firstly, it is important to note that your code must not depend on useMemo. In other words, you should be able to replace useMemo calls with direct function calls and not change anything in the application behavior, except the performance. The easiest way to do it is to write code without useMemo first, then add as needed.

To understand useMemo and when should you use it, check out this example. Firstly, look at this code

running along music video GIF by Columbia Records UK

🐱‍👤Setting Development Area

npx create-react-app hooksmemo

cd hooksmemo

npm start

After installation, we should have this.

I tried to think what would be the simplest app I could build to demonstrate the idea. One thing is for sure it should have a counter, and a button to increment that counter.

Let’s see the code

App.js

import logo from "./logo.svg";
import "./App.css";
import Container from "./Container";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          <b>useMemo</b> Skipping expensive recalculations.
        </p>
        <a
          className="App-link"
          href="https://hashnode.com/@Richa000"
          target="_blank"
          rel="noopener noreferrer"
        >
          Join me to Learn React
        </a>
        <Container />
      </header>
    </div>
  );
}

export default App;

I created Container file inside src folder then import that file inside App.js

import React, { useState } from "react";

const Container = () => {
  const [count, setCount] = useState(0);
  console.log("App rendered with count", count);

  const handleIncrementButton = () => {
    setCount(count + 1);
     };
  return (
    <div
      style={{
        width: "450px",
        height: "350px",
        border: "1px solid white",
        marginTop: "10px",
        textAlign: "center",
      }}
    >
      <h1>Hello Coders👩‍💻!</h1>
      <p style={{ color: "white" }}>Counter: {count}</p>
      <button
        onClick={handleIncrementButton}
        style={{
          background: "red",
          padding: "10px 15px",
          color: "white",
          border: "none",
        }}
      >
        Increment Count
      </button>
    </div>
  );
};

export default Container;

App.css

.App {
  text-align:start;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #282c34;
  width: 100vw;
  height: 100vh;
}

.App-logo {
  height: 20vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  display: flex;
  height: 100%;
  flex-direction: column;
  align-items:center;
  justify-content:flex-start;
  font-size: calc(10px + 2vmin);
  color: greenyellow;
  cursor: pointer;
}

.App-link {
  color: #f00b0b;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

Output:-

Try it. On every click of the increment button, the App component would re-render and the following console log will appear:-

Simple right? But we need to make it complicated then I will explain it to you in easily way😂. We want to add data fetching from a mock API, and a heavy calculation based on the fetched data.

The new code is highlighted:-

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

const Container = () => {
  const [count, setCount] = useState(0);
  const [data, setData] = useState();
  console.log("App rendered with count", count);

  console.log("App rendered with count", count);
  const handleIncrementButton = () => {
    setCount(count + 1);
  };

  const fetchData = () => {
    // Imagine here an API call which returns a random number
    return Math.random();
  };
  const runHeavyCalc = (data) => {
    if (!data) return;
    console.log("Computing heavy func with data", data);
    // Math.floor is not heavy, use your imagination again
    return Math.floor(data * 100);
  };
  useEffect(() => {
    const data = fetchData();
    setData(data);
  }, []);
  const result = runHeavyCalc(data);
  return (
    <div
      style={{
        width: "450px",
        height: "350px",
        border: "1px solid white",
        marginTop: "10px",
        textAlign: "center",
      }}
    >
      <h1>Hello Coders👩‍💻!</h1>
      <p style={{ color: "white" }}>Counter: {count}</p>
      <p style={{ color: "blue" }}>Result is {result}</p>
      <button
        onClick={handleIncrementButton}
        style={{
          background: "red",
          padding: "10px 15px",
          color: "white",
          border: "none",
        }}
      >
        Increment Count
      </button>
    </div>
  );
};

export default Container;

OK. What we’ve added here is a useEffect hook to fetch data from an API. Since the fetching is async by nature, we save the result to state when it returns (for brevity, written without async functions).

Then we run the runHeavyCalc function on it, and show the result on the screen

Now let’s click the button a couple of times and look at the console:

What happened here?🤔

It Happens Season 3 GIF by Portlandia

  1. The first message is printed twice because the App initially rendered with counter 0, then rendered again when the API fetch changed the state.

  2. Every counter increment button click re-rendered the App component, printing the new counter.

  3. Every render also ran the heavy calculation, printing the random number before calculation.

Korean Smile GIF

Why do we run the heavy calculation more than once, then?

useMemo to the rescue

Let’s look at the line calling the heavy calculation again:

const result = runHeavyCalc(data);

The answer is that React doesn’t know it shouldn’t run the calculation again. It doesn’t even know it’s a calculation; it’s just a line of code running on every render.

To make this calculation run only once, we’ll import the useMemo hook and refactor the line:

import React, { useEffect, useMemo, useState } from 'react';
...
const result = useMemo(() => runHeavyCalc(data), [data]);

Similar to the useEffect syntax, the passed callback will only run when a value changed in the dependency array — in our case when the data value changes.

With the new code, clicking the button a couple of times will result in the following console logs:

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

const Container = () => {
  const [count, setCount] = useState(0);
  const [data, setData] = useState();
  console.log("App rendered with count", count);
  const handleIncrementButton = () => {
    setCount(count + 1);
  };

  const fetchData = () => {
    // Imagine here an API call which returns a random number
    return Math.random();
  };
  const runHeavyCalc = (data) => {
    if (!data) return;
    console.log("Computing heavy func with data", data);
    // Math.floor is not heavy, use your imagination again
    return Math.floor(data * 100);
  };
  useEffect(() => {
    const data = fetchData();
    setData(data);
  }, []);
  //   const result = runHeavyCalc(data);
  const result = useMemo(() => runHeavyCalc(data), [data]);

  return (
    <div
      style={{
        width: "450px",
        height: "350px",
        border: "1px solid white",
        marginTop: "10px",
        textAlign: "center",
      }}
    >
      <h1>Hello Coders👩‍💻!</h1>
      <p style={{ color: "white" }}>Counter: {count}</p>
      <p style={{ color: "blue" }}>Result is {result}</p>
      <button
        onClick={handleIncrementButton}
        style={{
          background: "red",
          padding: "10px 15px",
          color: "white",
          border: "none",
        }}
      >
        Increment Count
      </button>
    </div>
  );
};

export default Container;

Here is my GitHub Link for the code:-

🎯 Wrap Up!!

Thank you for reading, I hope you enjoyed it🤩Please share it with your network. Don’t forget to leave your comments below.

Korean Drama Fighting GIF by The Swoon

Did you find this article valuable?

Support <YouCanCode/> by becoming a sponsor. Any amount is appreciated!