useCallback(); When and Why?

Sarat Chandra E
4 min readFeb 25, 2022

--

Spending some time on when and why to the useCallback() hook to optimize your code on performance.

What is it? useCallback() is a hook that returns a memoized callback that changes when any of the dependencies are changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders

const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);

Why useCallback()? Why memoize a function?
Consider the below simple snippet that increments and decrements a counter.

import React, { useEffect, useCallback } from 'react'
import { useState } from 'react';
const funccount = new Set();function ExplainUseCallback(props) {

const [counter, setCounter] = useState(0);
const increment = (e) => {
e.preventDefault()
setCounter(counter + 1)
}
const decrement = (e) => {
e.preventDefault()
setCounter(counter - 1)
}
funccount.add(increment)
funccount.add(decrement)
console.log(funccount.size)
return (
<div className='explain-use-callback'>
Counter : {counter}
<button onClick={increment}> increment </button>
<button onClick={decrement}> decrement </button>
</div>
)
}

In the above snippet, we have two inline handler functions “increment” and “decrement” and every time the component renders we add the functions to a Set “funccount”.
Now if you try to run the app and increment and decrement the operations you will find out that the length/size of the Set funccount keeps increasing by 2 on each handler action. That is because the functions do not equally match each other by reference and each time a new function is created.

Each function is getting created and deleted on each render of the component.

Now let's add one more button. Let's assume it’s one of the many UI components and states we have in our app. Let's call it a random button that updates some random state. Adding to our snippet that little bit of code to make a random button work.

//js
const [random, setRandom] = useState(0);
const updateRandomValue = (e) => {
e.preventDefault()
setRandom(random + 1)
}

//html
<button onClick={updateRandomValue}> Random Button</button>

Now try updating the random value. If you observe the length/size of the Set funccount it keeps incrementing, even though you have not passed any event on the increment and decrement handlers.

Now let's wrap our increment and decrement functions with useCallback hook.

const increment = useCallback((e) => {
e.preventDefault()
setCounter(counter + 1)
}, [counter])
const decrement = useCallback((e) => {
e.preventDefault()
setCounter(counter - 1)
}, [counter])

Now let's try to update the random value again, you will observe the length/size of the Set funccount doesn’t change. That is because useCallback() has memoized your function callback and doesn't create a new function again unless the dependency that we have passed has changed.

useCallback() memoizes the function callback with the dependencies passed and doesn't recreate the functions on each render for the same dependencies.
Whenever the memoized function is called with the same dependencies, it just returns the callback it has already memorized before. useCallback() is like useMemo() hook but for functions.

Why dependency for useCallback? We pass the dependencies to let it know when to create a new callback function. In the above example, whenever the counter changes, it creates a new callback function. If it is not passed, there will be only one memoized callback that it will return for any call and that might not be what is expected. Give a try once without the dependency and observe the behavior. If your function doesn't have a dependency on any state, that should be fine.

Clarifying a few open doubts:

Difference between useCallback with dependency and inline functions when both are newly created every time? Why useCallback when called again with the same dependency it creates a new function?
Though it is wrapped with useCallback and when the dependency is changed, it still returns a new callback function. But when you try to update some other state or rerender the component, it won't create a new function because none of the dependencies have changed.
Whereas an inline function gets created every time the component renders.
Still, you should go ahead with inline functions when you don't need a useCallback as they are usually cheap.

When to use useCallback()?
There are many good use cases like:

  • Works great with PureComponents.
  • You have a big list of data and you have a handler function on that state of data, and you don’t want to compute on the same state again and again.
  • on debounce, throttle, or such utility functions
  • passing down functions with no state updates

When not to use useCallback()? Why not wrap all my functions with useCallback()?
Though useCallback() is a good optimization task, you might not want to use it everywhere. Though your function might not get created every time, the useCallback will still have to run on every rerender.
Unless you have a use case on why you should use a useCallback, you can go ahead with the inline functions as they are pretty cheap and don't affect you on performance and optimization. Internally, React’s useCallback Hook compares the dependencies on every re-render to decide whether it should re-define the function. This kind of optimization can become more expensive than no optimization.

Use useCallback() only when you have a good use case for that. Otherwise, inline functions are cheap and good to go. Wrong use of optimization can become more expensive than no optimization.

--

--

No responses yet