Understanding of UseCallback React Hooks In Detail

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class. There are list of hooks which React community introduced.
The useCallback hook is used, when you have a component in which the child is re-rendering again and again without need. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
JavaScript provides three different value-comparison operations:
- === Strict Equality Comparison ("strict equality", "identity", "triple equals")
- == Abstract Equality Comparison ("loose equality", "double equals")
- Object.is(value1, value2) // return boolean
e.g.
var a = 10;
var b = 10;
console.log(a == b); // true
var c = 5;
var d = "5";
console.log(c == d); // true
const obj1 = {
first_name: "javascript",
};
const obj2 = {
first_name: "javascript",
};
console.log(obj1 == obj2); // false
Now, let's understand referential equality in case of function comparison
function func() {
return () => "JavaScriptFunc"
}
let javaScript1 = func();
let javaScript2 = func();
javaScript1 === javaScript2 // false
This is the key point to understand, when the component is re-rendered, functions defined inside function components are recreated each time, resulting in referential inequality .
Referential Equality -> We can say two objects are referentially equal when the pointers of the two objects are the same or when the operators are the same object instance.
To prevent creating the new instances of functions while re-rendering components, you can wrap the function inside useCallback. The useCallback() hook returns a memoized callback to maintain referential equality between renders of functions.
Now, let's deep dive and try to understand with react component:
import { useState, useEffect } from "react";
const Summation = () => {
const [userInput, setUserInput] = useState("");
const [result, setResult] = useState("");
const [num1, num2] = [4,5];
const sum = () => num1 + num2;
useEffect(() => {
console.log(`New Sum: ${sum()}`);
setResult(sum);
}, [sum]);
return (
<main className="App">
<input
type="text"
placeholder="input"
value={userInput}
onChange={(e) => setUserInput(e.target.value)}
/>
<h1>Output: {userInput || "--"}</h1>
<div>{result}</div>
</main>
);
};
export default Summation;
Here, userInput is a state and num1 and num2 are 2 constant which are not changing. Sum is function which return total of num1 and num2. We also have useEffect which is relying on sum function as dependency, so any time sum function will change, useEffect will run.
But if you try to type on input box, you will observe, how many letters you type in the textbox, same number of times console.log printed with same value

We also get this warning, which state, sum function is recreating on every re-render of component and making dependency in useEffect:

Moving sum function inside useEffect will limit the flexibility of reusing the function, thus wrapping it inside useCallback:
const sum = useCallback(() => num1 + num2, [num1, num2]);
Weird Part start here:
Let's rebuild the component with some changes:
import { useState, useEffect, useCallback } from "react";
const Summation = () => {
const [userInput, setUserInput] = useState("");
const [result, setResult] = useState(0);
const [num1, num2] = [4,5];
const sum = () => num1 + num2;
//const sum = useCallback(() => num1 + num2, [num1, num2]);
const buildArray = () => [num1, num2];
useEffect(() => {
console.log(`New array: ${buildArray()}`);
//setResult(buildArray());
}, [buildArray]);
return (
<main className="App">
<input type="text" placeholder="input" value={userInput} onChange={(e) => setUserInput(e.target.value)} />
<h1>Output: {userInput || "--"}</h1>
<p>Result: {JSON.stringify(result)}</p>
</main>
)
}
export default Summation
Here, same like previous, we will have same warning for buildArray function and useEffect will trigger when type on input box, same as earlier.
Bigger problem in this scenario is, since this is not primitive data type, React will not save us from ourselves and this is where beginners have a lots of problem because once they set the state and they have a function in the dependency that is recreated on every render. When this happens this will create endless rendering loop:
To demonstrate the real pain of this component, uncommenting setResult state inside useEffect
When user start typing in input
what are the common React re-rendering mistake a developer should avoid while using useCallback hooks.
useEffect(() => {
console.log(`New array: ${buildArray()}`);
setResult(buildArray());
}, [buildArray]);
!!!BOOM!!!

useCallback will be saviour here:
const buildArray = useCallback(() => [num1, num2], [num1, num2]);
Conclusion
Performance is really important when talking about React applications. UseCallback hooks is going to help you a lot when working with complex situations, calculations or with real big data to prevent unnecessary operations.
Thanks for reading 🙏

