React.memo , useMemo() and useCallback() explained
In this article, you will understand the actually use case of React.memo() , useMemo() and useCallback()
UseMemo()
- The value returned is stored in a cache, so that next time you can directly retrieve it from the cache.
- The useMemo Hook can be used to keep expensive, resource-intensive functions from needlessly running.
- The useMemo Hook only runs when one of its dependencies is updated, hence performance increases.
import { useState, useMemo } from "react";
const UseStateHook = () => {
const [counterOne, setCounterOne] = useState(0);
const [counterTwo, setCounterTwo] = useState(0);
const incrementOne = () => {
console.log("one");
setCounterOne(counterOne + 1);
};
//the expensive function is kept inside useMemo , and it onyl work when "counterOne" value changes
const isEven = useMemo(() => {
let i = 0;
console.warn("hllo");
while (i < 2000000000) i++;
return counterOne % 2 === 0;
}, [counterOne]);
const incrementTwo = () => {
console.log("two");
setCounterTwo(counterTwo + 1);
};
//useMemo - return value
//useCallBack - return entire function
console.log(isEven);
//this is expesive fucntion which is making the entire DOM rendering slow
// function isEven() {
// let i = 0;
// console.warn("hllo");
// while (i < 2000000000) i++;
// return counterOne % 2 === 0;
// }
return (
<>
<button onClick={incrementOne}>Counter One - {counterOne}</button>
//<p> {isEven() ? "even" : "odd"}</p>
<p> {isEven ? "even" : "odd"}</p>
<button onClick={incrementTwo}>Counter Two - {counterTwo}</button>
</>
);
};
export default UseStateHook;
In the above example –
- isEven() was an expensive function, hence it was affecting the entire DOM performance even though is not related to the other logic.
- So isEven is wrapped inside useMemo() and given a condition that this will only run when “counterOne” value changes,
- Now Counter Two button performance is not affected, hence entire dom performance increased
useCallBack()
You can copy the above function and replace “useMemo” with “useCallback” , and the function will work fine.
But for your better understanding, we will be dealing with a new example for useCallback .
Before writing code , you will notice we will also use React.memo() with useCallback , so let’s quickly understand this first
React.memo()
Some key points –
- Improves performance as it restricts unnecessary re-rendering of the component.
- Memoize(cache) a component hence faster reload
How to write memo()
To use memo , We usually wrap the export component within memo function
import React from "react";
const Title = () => {
console.log("title component render");
return <div>Title</div>;
};
export default React.memo(Title);
Now lets understand useCallback() with a code
Create 3 components –
Step 1 – Create 3 Compponent
- ParentComponent.jsx
- Button.jsx
- Count.jsx
Here the <ParentComponent /> will wrap the <Button/> and <Count/> Component component
Needless to say, the main <App /> component will be wrapping the <Parent/> component
Demo –
ParentComponent.jsx
------------------
<ParentComponent>
<Count/>
<Button/>
<ParentComponnt>
App.jsx
---------
<App>
<ParentComponent/>
</App>
Now that you understood the heirarchy , let write some code
ParentComponent.jsx –
import { useState } from "react";
import Count from "./Count";
import Button from "./Button";
const ParentComponent = () => {
const [age, setAge] = useState(25);
const [salary, setSalary] = useState(5000);
const incrementAge = () => {
setAge(age + 1);
};
const incrementSalary = () => {
setSalary(salary + 1000);
};
return (
<>
<Count text="age" count={age} />
<Button handleClick={incrementAge} count={age}>
Increase age
</Button>
<Count text="salary" count={salary} />
<Button handleClick={incrementSalary}>Increase Salary</Button>
</>
);
};
export default ParentComponent;
Button.jsx
import React from "react";
const Button = ({ handleClick, children }) => {
console.log("button component render ", children);
return (
<div>
<button onClick={handleClick}>{children}</button>
</div>
);
};
export default Button;
Count.jsx
import React from "react";
const Count = (props) => {
console.log("count component render", props.text);
return (
<div>
{props.text} - {props.count}
</div>
);
};
export default Count;
Output
-------
count component render age
button component render Increase age
button component render Increase salary
On first render , all 4 elements will be printed , as we have consoled all the elements
Why 4 times ?
-----------
<Count/> //for age
<Button>Increase Age</Button>
<Count/> //for salary
<Button>Increase Salary</Button>
Now if I click only on “Increase Age” button , you will see all the 4 elements again got re-rendered.
Similar thing happens , if I click on “Increase Salary” button
Well! to prevent this we can use React.memo() , which help in stopping the render of those component, whose props have not changed.
So let’s add that.
Button.jsx
import React from "react";
const Button = ({ handleClick, children }) => {
console.log("button component render ", children);
return (
<div>
<button onClick={handleClick}>{children}</button>
</div>
);
};
export default React.memo(Button);
Count.jsx
import React from "react";
const Count = (props) => {
console.log("count component render", props.text);
return (
<div>
{props.text} - {props.count}
</div>
);
};
export default React.memo(Count);
We don’t need to add React.memo() in the parent component.
Output
-------
count component render age
button component render Increase age
button component render Increase salary
We now you can see, as we click on increase age” button ,the <Count /> component render only once i.e only for age. This is what we wanted, But you can see the button for increase salary also render , which is not as expected.
Why is this happening?
In JavaScript, functions are objects, and like other objects, they are compared by reference, not by value. This means that two function expressions or declarations will only be equal if they refer to the same function object in memory, not just because they have the same body or behavior.
In the above example, even though dog1 and dog2 do the exact same thing, they are not considered equal because they are two separate function instances in memory.
const dog1 = function(){console.log('14/10')}; // has a unique object reference
const dog2 = function(){console.log('14/10')}; // has a unique object reference
console.log(dog1 == dog2); // false
console.log(dog1 === dog2); // false
How to fix them
If you want two variables to be considered equal when comparing them, they must refer to the same function object. You can achieve this by assigning the same function reference to both variables.
Here, dog1 and dog2 both point to the same function, so the comparison returns true
//Improving the above function with callback :
const dog1 = () => console.log("14/10");
const dog2 = () => console.log("14/10");
const doSomething = (cb) => {
cb();
};
doSomething(dog1);
doSomething(dog2);
console.log(doSomething(dog1) === doSomething(dog1));
We can achive the same thing in react by using useCallback
function MyComponent() {
// handleClick is re-created on each render
const handleClick = () => {
console.log('Clicked!');
};
// ...
}
In the above function, on every render a new handleClick function is created because inline functions are cheap, the re-creation of functions on each rendering is not a problem.
That’s when useCallback(callbackFun, deps) is helpful: given the same dependency values deps, the hook returns the same function instance between renderings (aka memoisation), meaning the function will have the same “referential equality”.
import { useCallback } from 'react';
function MyComponent() {
// handleClick is the same function object
const handleClick = useCallback(() => {
console.log('Clicked!');
}, []);
// ...
}
Another example to understand useCallback()
Let understand the issue in the below code –
The “handleText” function in the App.jsx has no relation with the users object , still whenever we change the input data , the setText state updated and hence all the child (List component in this case) will be re-rendered.
Nothing to worry here because this is how react works. Whenever we change a parent state , the child component also re-render.
This is generally fixed by React.memo()
Note : React.memo() is not linked with useMemo().
List.jsx
import React from "react";
const List = ({ users, onRemove }) => {
console.log("rendering list");
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} <span onClick={() => onRemove(user.id)}>X</span>
</li>
))}
</ul>
);
};
export default React.memo(List);
App.jsx
import { useCallback, useState } from "react";
import List from "./components/List";
const initialUsers = [
{
id: "1",
name: "foo",
},
{
id: "2",
name: "bar",
},
{
id: "3",
name: "loo",
},
];
const App = () => {
const [users, setUsers] = useState(initialUsers);
const [text, setText] = useState("");
const handleRemove = (userId) => {
console.log("handleRemove", userId);
const filteredUsers = users.filter((user) => user.id !== userId);
setUsers(filteredUsers);
};
//this function has no relation with the users object
const handleText = (event) => {
setText(event.target.value);
};
return (
<div>
<input type="text" value={text} onChange={handleText} />
<List users={users} onRemove={handleRemove} />
</div>
);
};
export default App;
But hope you have seen till now, the issue is still not fixed.
Its because , Every time we change the input , the App component re-renders , and hence everytime a new handleRemove() function is created in react , and that in turn is calling the List component again and again.
The fact that here React.memo() also failed is because memo() always compares the earlier state change with the new one, if anything is changes , it will re-render.
And here since every time a new inline function is created , everytime React.memo() finds a new function and hence, it initiated a new re-render.
Just wrap the handleRemove() function with callback
const handleRemove = useCallback(
(userId) => {
console.log("handleRemove", userId);
const filteredUsers = users.filter((user) => user.id !== userId);
setUsers(filteredUsers);
},
[users]
);
Key Takeaways and Summary Comparison
React.memo is a higher-order component (HOC) that prevents a functional component from re-rendering if its props haven’t changed.
useMemo memoizes the result of a function, preventing it from being recalculated on every render unless its dependencies change.
useCallback memoizes a function, preventing it from being recreated on every render unless its dependencies change.
| Tool / Hook | Primary Purpose | What It Caches / Memoizes | When to Use |
|---|---|---|---|
| React.memo() | Higher-Order Component (HOC) | Component Render Output | Prevents re-rendering a component if its incoming props have not changed. |
| useMemo() | React Hook | Result of a Computation / Value | Avoids expensive re-computations on every render unless dependencies change. |
| useCallback() | React Hook | Function Instance / Reference | Maintains function reference across renders to preserve prop equality for React.memo(). |