In this blog, we will see React js useCallback hook and how we can use it.
What is useCallback() hook?
React useCallback has a specific function: avoid unnecessary re-renders in your code, making your application faster and more efficient. It looks as follow:
const memoizedCallback = useCallback(() => {
doSomething(a, b);
},[a, b]);
The hook takes 2 arguments: a callback function and dependency array. It returns a memoized version of that callback function that only changes if one of the dependencies has changed.
Basically function wrap inside useCallback() hook. It tells react to not re-create a wrapped function when component re-renders, unless any value of the useCallback's dependency array is changed.
First, let's see an example
Example app
Take a look at the sample app. App.js
has an input field that takes a number and returns factors of that number. The toggle button changes the theme. Lists.js
component display factors.
App.js
import { useState, useCallback } from "react";
import { List } from "./components/List";
import './App.css';
function App() {
const [number, setNumber] = useState(0);
const [isDark, setDark] = useState(false);
const getFactors = () => {
let factors = []
for(let i = 1; i <= number/2; i++) {
if(number % i === 0) {
factors.push(i)
}
}
return factors
}
const theme = {
color: isDark ? "#ffffff" : "#333333",
backgroundColor: isDark ? "#333333" : "#ffffff"
}
return (
<div style={theme} className="App">
<input type="number" placeholder="number" onChange={(e) => setNumber(e.target.value)}/>
<button onClick={() => setDark(prevState => !prevState)}>Toggle Theme</button>
<List getFactors={getFactors}/>
</div>
);
}
export default App;
As shown in code, all states and functions are in App.js
. Child component List.js
acts as a container to show data.
List.js
import { useState, useEffect } from "react";
export const List = ({getFactors}) => {
const [items, setItems] = useState([]);
// will update
useEffect(() => {
console.log("Calculating... (from list)")
setItems(getFactors());
},[getFactors])
return (
<ul>
{
items?.map((item) => (
<li key={item}>{item}</li>
))
}
</ul>
)
}
In the List.js
component, we have useEffect which would be called whenever the getFactors()
function is updated.
If we run our app, notice that When we give input or change input the console logs Calculating... (from list)
as we expect. But the problem is that when we toggle the theme it also calls that getFactors() function, console logs Calculating... (from list)
.
The reason we are running into this problem is that In React, whenever a component re-renders, a new instance of the function in it gets generated. So the getFactors()
function inside our App.js
gets re-created every time when App.js
is re-rendered. A new instance of the getFactors()
is created even if we don't change the input value.
Solution
We can use the useCallback hook to prevent this. useCallback will not re-run code wrap inside of it unless the dependency array is changed. So the function will only re-run when it needs to. For example, when changing input value. So let's import the useCallback hook.
import { useCallback } from "react";
and wrap our getFactors()
function in useCallback() and add dependency which is number
.
const getFactors = useCallback(() => {
let factors = []
for(let i = 1; i <= number/2; i++) {
if(number % i === 0) {
factors.push(i)
}
}
return factors
},[number])
Result
Now notice, when we Toggle Theme it is doesn't console logs Calculating... (from list)
. getFactors()
function will only be re-created when dependency array is changed, that is number
is changed.
CodeSandBox
When to use useCallback?
In most cases, re-creating function instances is cheap, it won't affect the application. But, warping every function inside useCallback will cause performance issues, instead of solving them. useCallback will be called on every re-render and we also have to declare the dependency array.
So when to actually use it?
- Referential Equality
Functions are objects in javascript. An example to understand object references.
const sum = (a, b) => {
return (a + b)
}
const sum1 = (1 + 2); // has a unique object reference.
const sum2 = (1 + 2) // has a unique object reference.
sum1 === sum2 // false
Here, sum1
and sum2
share the same source code but they are different function objects. Compare sum1 === sum2
evaluates to false
.
- Re-computation is expensive
Imagine you have a function that returns a list, containing hundreds of items. Re-creating that function and rendering would be expensive. so we can use useCallback here.
Conclusion
- useCallback hook is useful to avoid unnecessary re-renders in your code, making your application faster and more efficient.
- Re-creating functions is cheap, it won't affect the application. But, warping every function inside useCallback will cause performance issues.