In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
This article will explore how re-rendering work optimization and performance in focus using useMemo
and useCallback
hooks. A basic understanding of React before starting this tutorial. You can learn more about React hooks from the official docs here.
useMemo() hook
useMemo
stands for memorization which cases a value so that we don't recompile every time. It is important we don't memoize everything to avoid performance overhead because the side effect will that it calls every single re-render of components causing additional memory usage and performance problems.
useMemo
is built-in Hooks introduced in 16.8 that accepts two arguments that render function with dependencies array.
user
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const calculateValue = useMemo(() => {
return expensiveCalc(users);
}, [users])
useMemo
invokes users
at the initial rendering and memoizes users
at re-rendering for the component.
let's look at these examples and the difference when applied with uesMemo
import React, { useState } from "react";
const FirstComponent = () => {
const [count, setCount] = useState(0);
const [numbers, setNumbers] = useState(0);
const isEven = () => {
let i = 0;
while (i < 3000000) i++
return numbers % 2 === 0
}
return (
<div>
<div>
<button
onClick={() => setCount(count + 1)}>
Counters - {count}
</button>
</div>
<div>
<button
onClick={() => setNumbers(numbers + 1)}>
Numbers - {numbers}
</button>
<span>{isEven() ? "Even" : "Odd"} </span>
</div>
</div>
)
}
export default FirstComponent;
Every time we click on either button, there is a re-renders which recalculates the count
and numbers
functions. However, we don't need all buttons to re-render at every click but the one with heavy calculation.
`useMemo can help in handling this heavy calculation with better-optimized performance.
import React, { useState, useMemo } from "react";
const FirstComponent = () => {
const [count, setCount] = useState(0);
const [numbers, setNumbers] = useState(0);
const isEven = useMemo(() => {
let i = 0;
while (i < 3000000) i++;
return numbers % 2 === 0;
}, [numbers]);
return (
<div>
<div>
<button onClick={() => setCount(count + 1)}>Counters - {count}</button>
</div>
<div>
<button onClick={() => setNumbers(numbers + 1)}>
Numbers - {numbers}
</button>
<span>{isEven ? "Even" : "Odd"} </span>
</div>
</div>
);
};
export default FirstComponent;
Every time we change the value of "counters" the value is displayed as expected.
However, if we click the "numbers" button "counters" does not re-render because number
useMemo(() => { let i = 0; while (i < 3000000) i++; return numbers % 2 === 0; }, [numbers])
returns the memoized calculation.
useCallback() hook
useCallback
is a callback hook used when a child component is rerendering over and over again without any need.
useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.
Functions in JavaScript are first-class citizens, meaning that a function is a regular object.
Using JavaScript equality check below
true === true // true
false === false // true
3 === 3 // true
'c' === 'c' // true
{} === {} // false
[] === [] // false
() => {} === () => {} // false
const m = {}
m === m // true
The inline function works better when we run a function per component but when multiple functions are wrapped within components like "useEffect" or "useMemo", this is where implementation of useCallback
can be effectively used.
import ParentCompoents from "./components/ParentComponent";
const countFunction = new Set();
const = App () => {
const [count, setCount] = React.useState("");
const [user, setUser] = React.useState([]);
const handleUser(newUser) {
setUser([newUser, ...user]);
}
countFunction.add(handleUser);
console.log(countFunction)
return (
<>
<ParentComponent user={user} handleUser={handleUser} />
<button onClick={() => setCount(prev => prev + 1)}>{count} + </button>
<button onClick={() => setCount(prev => prev - 1)}>{count} - </button>
</>
);
}
Every time the state updates and our app re-renders the handleUser
callback we pass down the component that is recreated. This is not something to worry about but If we are to handle multiple from Facebook, we might be updating our app quite a lot.
To solve this, we use a useCallback hook to handle the state efficiently.
import ParentCompoents from "./components/ParentComponent";
const countFunction = new Set();
const = App () => {
const [count, setCount] = React.useState("");
const [user, setUser] = React.useState([]);
const handleUser = React.useCallback((newUser) {
setUser([newUser, ...user]);
}, [user] );
countFunction.add(handleUser);
console.log(countFunction)
return (
<>
<ParentComponent user={user} handleUser={handleUser} />
<button onClick={() => setCount(prev => prev + 1)}>{count} + </button>
<button onClick={() => setCount(prev => prev - 1)}>{count} - </button>
</>
);
}
Now can handle our data gracefully only when the state changes.
Conclusion
useMemo and useCallback is quite a complex topic and that may only be required on a large-scale project with huge computational calculations.
Here are references to Kent C. Dood and React Docs for further research.
Please share your thought and leave a like :)