A simple JS implementation for Memoization would be as follows :
const expensiveFunction = (num) => {
console.log("recalculation done");
for (let i = 0; i < 100000; i++) { }
return num + 2;
}
expensiveFunction(2) // prints recalculation done
expensiveFunction(2) // prints recalculation done
Output :
recalculation done
recalculation done
const cache = {};
const expensiveFunction = (num) => {
const givenArg = JSON.stringify(num)
if (cache[givenArg]) {
return cache[givenArg]
}
else {
console.log("recalculation done");
for (let i = 0; i < 100000; i++) { }
cache[num] = num + 2
return num + 2;
}
};
expensiveFunction(2) // prints recalculation done
expensiveFunction(2) // does not print anything since else part is not executed
Additionally , when the parent re-renders then so does the child component irrespective of whether its props or state were changed and when this happens in a fairly large project then performance suffers to tackle this issue we use memorization and it can be achieved using three APIs:
- React.memo()
- useMemo()
- useCallback()
Using React.memo() for Memoization:
React.memo()
is a higher order component (HOC) that returns a new component only if the props of the component with which we have called it are changed. This way the child component will no longer re-render even if the parents component does.
Using React.memo()
// ParentComponent.jsx
import { useState } from "react";
import MemoChildComponent from "./ChildComponent";
const ParentComponent = () => {
const [counter, setCounter] = useState(0);
const name = "poornima";
const clickHandler = () => {
setCounter((prev) => prev + 1);
};
return (
<>
<p>{counter}</p>
<button onClick={clickHandler}>counter</button>
<MemoChildComponent propVal={name} />
</>
);
};
export default ParentComponent;
// Child component
import React from "react";
const ChildComponent = ({ propVal }) => {
console.log("child component rendered", propVal);
return <></>;
};
const MemoChildComponent = React.memo(ChildComponent);
export default MemoChildComponent;
Output:
<MemoChildComponent propVal={name} /> to <MemoChildComponent propVal={someFunc} />
Then the child component will get re-rendered just like how it happened when we didn't use React.memo() Similarly, if we pass some array say const users=["John Doe", "Jane Doe"]
useCallback() is a hook and it takes two arguments - a callback function and a dependency array, the function is recreated only when the dependency array changes Now let us try to use this in the previous example:
The ChildComponent remains the same let's make some changes to the ParentComponent where we create memoized function and pass it as prop:
// ParentComponent.jsx
import { useCallback, useState } from "react";
import MemoChildComponent from "./ChildComponent";
const ParentComponent = () => {
const [counter, setCounter] = useState(0);
const clickHandler = () => {
setCounter((prev) => prev + 1);
};
const add2 = () => {
return 1 + 1;
};
const propFunc = useCallback(() => add2(), []);
return (
<>
<p>{counter}</p>
<button onClick={clickHandler}>counter</button>
<MemoChildComponent propVal={propFunc} />
</>
);
};
export default ParentComponent;
This time the component is not re-rendered , because we have given an empty array as dependency which means the function is created only on the first render and the value of propFunc
never changes.
Using useMemo() for memoization:
Just like useCallback() , useMemo() has two arguments a callback function and a dependency array, and the function is recreated when the dependency array value changes, the only difference is that is that useCallback() memoizes a function whereas useMemo() memoizes the value returned by the function. Let us look at the code example to understand it better:
The ChildComponent remains the same let's make some changes to the ParentComponent where we create memoized array value by using useMemo() and pass it as a prop:
import { useState, useMemo } from "react";
import MemoChildComponent from "./ChildComponent";
const ParentComponent = () => {
const [counter, setCounter] = useState(0);
const clickHandler = () => {
setCounter((prev) => prev + 1);
};
const users = useMemo(() => ["John Doe", "Jane Doe"], []);
return (
<>
<p>{counter}</p>
<button onClick={clickHandler}>counter</button>
<MemoChildComponent propVal={users} />
</>
);
};
export default ParentComponent;
OutPut: