React memo() vs useMemo() vs useCallback()
Think of memoization as caching a value/Component so that it does not need to be recalculated/re-rendered.
All of them can improve performance, but let's dive into each one to find out the differences:
memo():
memo() is a Higher Order Component. It will cause React to skip rendering a component if its props have not changed.
Problem is:
In this example, Countries
component re-renders even if countries prop have not changed ( open console on devtools and see the child component logs ):
import {useState} from 'react';
import Countries from "./Countries";
export default function App() {
const [count,setCount] = useState(0);
const [countries,setCountries] = useState(['Iran','china'])
return (
<div className="App">
<h1>Parent Component</h1>
<div>Count: {count}</div>
<button onClick={()=>setCount(c=>c+1)}>Increment</button>
<Countries countries={countries}/>
</div>
);
}
// child component
const Countries = ({ countries }) => {
console.log("Rendering Child Component...");
return (
<>
<h1>Countries</h1>
{countries?.map((country, index) => (
<div key={index}>{country}</div>
))}
</>
);
};
export default Countries;
Solution is:
To fix this, we can use memo()
. Wrap the Countries
component export in memo
:
// Countries.js
import {memo} from 'react';
// rest
export default memo(Countries) //here
useMemo():
It returns a memoized value.
Let's find out what that means in an example.
Problem is:
In this example the calculation
function is artificially slowed down so that you can see what happens when you increase and update count
state or even add a country because of rendering on each event:
import { useState } from "react";
const calculation = (num) => {
console.log("Calculating...");
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};
export default function App() {
const [count, setCount] = useState(0);
const [countries, setCountries] = useState([]);
const calculating = calculation(count);
const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setCountries((c) => [...c, "Iran"]);
};
return (
<div>
<div>
<h2>Countries</h2>
{countries.map((country, index) => {
return <p key={index}>{country}</p>;
})}
<button onClick={addTodo}>Add Country</button>
</div>
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
<h2>Calculation</h2>
{calculation}
</div>
</div>
);
}
Solution is:
To fix this performance issue, we can use the useMemo
to memoize the calculation
function. This will cause the function to only run when count
changed for example.
import { useState, useMemo } from "react";
// rest
const calculating = useMemo(() => calculation(count), [count]); //here
// rest
Now, you can see there is no delay when you add country. But not when count
changed.
useCallback():
It returns a memoized callback function.
Note: The useCallback
and useMemo
Hooks are similar. The main difference is that useMemo
returns a memoized value and useCallback
returns a memoized function.
Back to the first section. memo()
prevented the re-rendering of Countries
component.
But what if we pass another prop called addCountry
:
import { useState } from "react";
import Countries from "./Countries";
export default function App() {
const [count, setCount] = useState(0);
const [countries, setCountries] = useState(["Iran", "china"]);
const addCountry = () => {
setCountries((c) => [...c, "Japan"]);
};
return (
<div className="App">
<h1>Parent Component</h1>
<div>Count: {count}</div>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
<Countries countries={countries} addCountry={addCountry} />
</div>
);
}
// Child Component
import { memo } from "react";
const Countries = ({ countries, addCountry }) => {
console.log("Rendering Child Component...");
return (
<>
<h1>Countries</h1>
{countries?.map((country, index) => (
<div key={index}>{country}</div>
))}
<button onClick={addCountry}>Add Country</button>
</>
);
};
export default memo(Countries);
Problem is:
Try to click Increment button to update count
state. You can see Countries
component re-rendered again although we wrapped it with memo()
.
Solution is:
To fix this, we can use the useCallback
hook to prevent addCountry
from being recreated unless necessary. It is enough to change addCountry
function to this:
// App.js
import { useState, useCallback } from "react";
// rest
const addCountry = useCallback(() => {
setCountries((c) => [...c, "Japan"]);
}, []);
// rest
Now the Countries
component re-rendered only when countries prop updated.
I hope this article helps anyone who confused using these hooks.
Thanks for your time ๐๐ฅฑ.