🎯 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.
🔑Definition:-
useMemo — returns a memoized value, which helps avoiding expensive calculations on every render.
What problem does useMemo solve?🧐
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
:
A calculation function that takes no arguments, like
() =>
, and returns what you wanted to calculate.A list of dependencies including every value within your component that’s used inside your calculation.
When should you use it?🤷♀️
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
🐱👤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?🤔
The first message is printed twice because the App initially rendered with counter 0, then rendered again when the API fetch changed the state.
Every counter increment button click re-rendered the App component, printing the new counter.
Every render also ran the heavy calculation, printing the random number before calculation.
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.