The React useCallback hook

Published October 13, 2021

The React useCallback hook is meant to be used for performance reasons. The React docs state that useCallback returns a memoized callback. What does this mean?

Memoization

Memoization is like caching. It's a type of cache for the value that is returned from a function.

How does memoization come into play with useCallback? Let's take a closer look. When you run a function for a second time using the same arguments as the first time, memoization will immediately return the result that it remembers from the first time that function ran. The function doesn't end up having to be run the second time, because it's been cached. This is where the performance benefit is found.

The memoization of useCallback is similar to storing the result of a function call into a variable and then using the variable rather than calling the function every time.

Our brains also do memoization. When someone asks you, "What's 2 plus 2?", you don't compute it, you already know the answer is 4. The same goes for useCallback and memoization.

The function equality problem

Functions in JavaScript are actually objects. Take a look at the following example where a sum() function returns a function that sums two numbers.

function sum() {
  return (a, b) => a + b;
}

const sumA = sum();
const sumB = sum();

sumA(1, 2); // 3
sumB(1, 2); // 3

sumA === sumB; // false
sumA === sumA; // true

sumA and sumB are both functions that sum two numbers. They were both created using the sum() function but they represent different function objects. That's why when we compare them using sumA === sumB, it evaluates to false.

Remember that in JavaScript, functions are objects, and an object is only equal to itself. This means that every time a React component re-renders, its functions are re-created as brand new functions. When re-created, these brand new functions re-run, even if we already know their result from the previous component render.

As we will see, useCallback allows us to tell React not to re-run a function every time a component re-renders.

The problem useCallback solves

Take a look at this simple component.

import { useCallback } from 'react';
function SimpleComponent() {

  const handleClick = () => {
    console.log('Clicked');
  };
}

Every time the above component re-renders, the handleClick function is re-created. It is a different function object every time that will re-run and output "Clicked" to the console.

The re-creation of functions on each component re-render is usually not a problem. Having a few inline functions per component is fine. However, in some cases, preventing the re-running of functions during component re-renderings is important for performance reasons.

The useCallback hook returns a memoized (or cached) instance of the function provided to it. This function instance will only change and re-run if one of the dependencies of the useCallback hook has changed. If not, it won't re-run between component re-renders. Instead of recreating a function object on every component re-render, useCallback allows us to use the same function object across component re-renders.

How to use the useCallback hook

useCallback(callbackFunction, [dependencies])

When the dependencies have the same values, the useCallback hook will return the same callbackFunction instance between re-renderings. When the dependencies change, the useCallback will

const startValue = 0;
const increment = useCallback(() => startValue + 1, [startValue])

In this example, the useCallback hook has memoized/cached the function () => startValue + 1. It will remember this function until the value of the dependency startValue changes. When starValue changes, then a new function instance will be created.

https://www.robinwieruch.de/react-usecallback-hook/

import { useCallback } from 'react'; function MyComponent() { // handleClick is the same function object const handleClick = useCallback(() => { console.log('Clicked!'); }, []); // ... } handleClick variable has always the same callback function object between renderings of MyComponent.

When to use the useCallback hook

///////////////////// ● However, parent components can end up having child components that get re-rendered again and again unnecessarily

Why do we need useCallback?

The reason we sometimes need to use useCallback is because in React, components render and re-render.

This means that if we have a function in our component, when that component re-renders, the result of that function is lost. We will need to re-run the function to get its result, unless we cache it for the next render.

So if you have created a function or a variable in your component, the second it gets rerendered all the values will be lost unless you persist it into the next render, which is exactly what react state does.

useMemo and useCallback both use this so that the results and functions you pass them do not constantly get called again and again.

If you need a function to persist between renders then you would need useCallback so it remembers that instance of the function. You can think of this a bit like creating a function outside the scope of your react component, so the function persists even when your code is rerendered.

When I say computationally expensive, all it really means is anything that is going to be resource heavy and take a lot of time or space to run.

For example a nested loop (O(n²) in Big O notation) over a moderate to large array might be something that you would consider to be “computationally expensive”.

Receives a function as a parameter, and an array of dependencies (similar to useEffect). useCallback return a memoized version of the callback function. it returns a cached version of a function. It receives an inline callback function and an array of dependencies.

It will return a memoized version of the callback that only changes if one of the dependencies has changed

accept a function and an array of dependencies.

dependencies

The idea is that you provide the array with values/variables that you want to use inside the function that you also provide to the hook.

export default function MyReactComponent({ myNumber }) { useEffect(() => { // I will be run every time myNumber changes console.log(myNumber) }, [myNumber])

const calculation = useMemo(() => { // I will also be run every time myNumber changes return myNumber * 2 }, [myNumber])

const calculate = useCallback(() => { // I will also be run every time myNumber changes return myNumber * 2 }, [myNumber])

return } If “myNumber” changes then the functions will be run again with the new value.

This ensures that you will always have up to date values in your code when you use these hooks so you don’t need to worry about stale (old values) variables whenever the hook gets run again.

Should I use useCallback for all functions?

● This hook has to compare the dependencies from the dependency array for every re-render to decide whether it should re-define the function. ● The computation for this comparison can be more expensive than just re-defining the function. ● useCallback() can increase code complexity. ● Use it only where it actually improves performance. ● Note: the useCallback() hook is still called every time a component re-renders. ○ It just returns the same cached function reference.

Conclusion

, useMemo and useCallback, are probably one of the biggest causes of confusion when you come across them compared to any other hook.

Re-rendering lists of items, which can grow to be very large, can create performance issues. ● Re-renderings of this lightweight component won't create performance issues.

Every line of code which is executed comes with a cost. do not use useCallback everywhere can cause perf prob.

the useCallback version is doing more work. Not only do we have to define the function, but we also have to define an array ([]) and call the React.useCallback which itself is setting properties/running through logical expressions etc.

So in both cases JavaScript must allocate memory for the function definition on every render and depending on how useCallback is implemented, you may get more allocation for function definitions (this is actually not the case, but the point still stands).

Performance optimizations are not free. They ALWAYS come with a cost but do NOT always come with a benefit to offset that cost.

Therefore, optimize responsibly.

MOST OF THE TIME YOU SHOULD NOT BOTHER OPTIMIZING UNNECESSARY RERENDERS. React is VERY fast and there are so many things I can think of for you to do with your time that would be better than optimizing things like this.