When I first read about the useCallback
hook I thought I had a great weapon with me to optimise my React app's performance and started using it on every damn function without understanding the limitations or may be I should call it the right concept behind it.
Before diving deep into this topic, let us first understand on a high level what exactly is the useCallback
hook.
So, basically useCallback
hook takes a function and a dependency array. It returns the memoized function. A new memoized value of this function is created whenever the value or references of the elements in the dependency array change.
What if you do not wrap a function using useCallback
?
When you don't wrap a function with useCallback
, whenever the component is re-rendered a new instance of the function is created(The function is given a new memory location).
Also, make a note of the below snippet.
function add() {
return (a, b) => a + b;
}
const add1 = add();
const add2 = add();
add1(1, 2); // 3
add2(1, 2); // 3
add1 === add2; // false
In the above snippet you can see that though add1 and add2 are created from the same function declaration and give the same output they are not the same because the references of these two functions are different.
When to use useCallback
?
Let us consider an example.
function Child({ handler }) {
return (
<div onClick={handler}>
Click Me
</div>
);
}
export default React.memo(Child)
Below is the Parent component
export default function ParentComponent() {
const [state, setState] = useState(false);
const [dep] = useState(false);
const handler = useCallback(
() => {
console.log("You clicked handler")
},
[dep]
);
const statehanddler = () => {
setState(!state);
};
return (
<>
<button onClick={statehanddler}>State Change</button>
<Child handler={handler} />
</>
);
In the above example we have wrapped the Child component with React.memo
which means it will re-render the child component only if the props to it change.
handler
is passed as a prop to the Child component.
Let us assume we did not use useCallback
in the above example.
In this case whenever we click the State Change button the value of state
is changed and the parent component is re-rendered. Since, at every re-render there will be a new instance of every function created we would have a new instance of the handler function.
Now, what would happen to the child component? Will it re-render?
In the add
example I have shown you how does function equality works. By referring to it we can say that the child component will re-render because the handler
prop now has a new reference. This means that even when we wrap the component with React.memo
we are a re-rendering the child component.
Assuming we are using useCallback
useCallback
hook here will memoize the function passed to it as an argument and it will only create a new instance of the memoized function if the value or reference to an element in the dependency array changes.
So, clicking on the State Change button will change the value of the state variable state
but the value inside the dependency array(dep
) remains same. Hence, there is no new instance of handler created and the child component will not re-render.
When not to use useCallback
?
useCallback
has its own downsides. There are times when using useCallback
makes no sense.
Let us take an example
export default function Parent() {
const clickHandler = useCallback(() => {
console.log('Click event')
}, [])
return <Child onClick={clickHandler} />
}
const Child = ({ clickHandler }) => {
return <button onClick={clickHandler}>Child Component</button>
}
In the above example using useCallback
makes no sense since we are creating clickHandler
function on every re-render. Also, optimization might cost us more here because of the useCallback
check we have to do on every re-render(Recreation of inline functions is generally cheap).
Conclusion
useCallback
memoizes functions instead of values, to prevent recreation upon every render. It helps us avoid unnecessary re-rendering and improves performance.
We also should be careful while using useCallback
because it can cost us a lot if we do not scan our components well before using it.
I hope this helps. If you have any questions and suggestions reach me out on Github and LinkedIn.
Follow me on Twitter
Have a nice day :)
Top comments (1)
Thanks for your useful information.