Beginners guide to useMemo
Intro
In this post we’re going to learn about what useMemo hook is and when should we use it in our application. Then we learn how to implement it and make sure it improves our app’s performance instead of making it worse.
Concept
In react, everything is based on state changes. Every state change inside a react component makes it re-render itself, which means invoking every variable or function declaration inside the component. Now imagine a performance heavy function that gets computed in every render and each time returns the same value. Until one of the props in that function changes, it will return the same result on each render and cause performance reduction in your app. useMemo is a react hook that enables you to memoize a function. It means that your function runs on the component initialization and the returned value will be stored so when the component state changes and DOM gets re-rendered, it can use the stored value instead of computing the function.
Implementation
The useMemo hook accepts a function and an array of dependency and returns a memoized value.
useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
For better understanding, imagine a user list component with more than ten thousand entries (although this should never happen in real world projects) which returns a table of all the entries (users) and provides a gender based filter. Every time we switch gender, the table would re-render.
import "./styles.scss";
import UserList from "./components/user-list";
export default function App() {
return (
<div className="main-container">
<UserList /> </div>
);
}import React, { useState } from "react";
import { userData } from "../json-data/data";
const UserList = () => {
const [gender, setGender] = useState("male");
const userDataFiltered = (
<>
{userData
.filter((item) => item.gender === gender)
.map((item) => (
<tr>
<td>{item.index + 1}</td>
<td>{item.name}</td>
<td>{item.gender}</td>
<td>{item.company}</td>
<td>{item.isActive ? "yes" : "no"}</td>
</tr>
))}
</>
);
return (
<div className="list-container">
<div className="filter-container">
<div className="gender-toggle">
<span>Male</span>
<input
type="checkbox"
id="switch"
onChange={() => {
setGender(gender == "male" ? "female" : "male");
}}
checked={gender == "male" ? true : false}
/>
<label for="switch">gender</label>
<span>Female</span>
</div>
</div>
<table class="rwd-table">
<tr>
<th>index</th>
<th>Name</th>
<th>Gender</th>
<th>Company</th>
<th>Active</th>
</tr>
{userDataFiltered}
</table>
</div>
);
};
export default UserList;The userData provided to the component is an array of objects:
export const userData = [
{
"_id": "6137b0fd82ad3eca732c7e4b",
"index": 0,
"guid": "730d565c-20d7-49e9-80f3-8b1e8e0f0eff",
"isActive": true,
"balance": "$1,889.25",
"name": "Mendez Arnold",
"gender": "male",
"company": "HATOLOGY"
},
...
]Now we add another state to this component, for example the value of a controlled input called tableName:
import React, { useState } from "react";
import { userData } from "../json-data/data";
const UserList = () => {
const [gender, setGender] = useState("male"); const [tableName, setTableName] = useState(null);
const userDataFiltered = (
<>
{userData
.filter((item) => item.gender === gender)
.map((item) => (
<tr>
<td>{item.index + 1}</td>
<td>{item.name}</td>
<td>{item.gender}</td>
<td>{item.company}</td>
<td>{item.isActive ? "yes" : "no"}</td>
</tr>
))}
</>
);
return (
<div className="list-container">
<div className="filter-container">
<div className="table-name"> <input value={tableName} onChange={(e) => setTableName(e.target.value)} placeholder="Type a name for this list..." /> </div> <div className="gender-toggle">
<span>Male</span>
<input
type="checkbox"
id="switch"
onChange={() => {
setGender(gender == "male" ? "female" : "male");
}}
checked={gender == "male" ? true : false}
/>
<label for="switch">gender</label>
<span>Female</span>
</div>
</div>
<table class="rwd-table">
<tr>
<th>index</th>
<th>Name</th>
<th>Gender</th>
<th>Company</th>
<th>Active</th>
</tr>
{userDataFiltered}
</table>
</div>
);
};
export default UserList;Notice that when we change the value of tableName, it takes a few seconds for the input to update. This is because when the tableName state changes, react has to re-render the component, this means our table with more than ten thousand entries should get filtered based on gender and returned every time we press a key.
Now what if there was a way to change tableName without filtering and rendering the table? To achieve this functionality we need to memoize our table so only when we call setGender the process of filtering and rendering happens.
We have to do some changes in user-list.js:
import React, { useMemo, useState } from "react";import { userData } from "../json-data/data";
const UserList = () => {
const [gender, setGender] = useState("male");
const [tableName, setTableName] = useState(null);
const userDataFiltered = useMemo(() => { return ( <>
{userData
.filter((item) => item.gender === gender)
.map((item) => (
<tr>
<td>{item.index + 1}</td>
<td>{item.name}</td>
<td>{item.gender}</td>
<td>{item.company}</td>
<td>{item.isActive ? "yes" : "no"}</td>
</tr>
))}
</>
);
}, [gender]);
return (
<div className="list-container">
<div className="filter-container">
<div className="table-name">
<input
value={tableName}
onChange={(e) => setTableName(e.target.value)}
placeholder="Type a name for this list..."
/>
</div>
<div className="gender-toggle">
<span>Male</span>
<input
type="checkbox"
id="switch"
onChange={() => {
setGender(gender == "male" ? "female" : "male");
}}
checked={gender == "male" ? true : false}
/>
<label for="switch">gender</label>
<span>Female</span>
</div>
</div>
<table class="rwd-table">
<tr>
<th>index</th>
<th>Name</th>
<th>Gender</th>
<th>Company</th>
<th>Active</th>
</tr>
{userDataFiltered}
</table>
</div>
);
};
export default UserList;You can see that without any effort, the userDataFiltered is memoized and won't be invoked until its dependency array items (gender) change. Now if we change the value of our input, a huge performance increase can be noticed as it updates instantly.
Conclusion
See the example in codesandbox