Skip to content

The useCallback Hook

Alright, so that just about covers useMemo… what about useCallback?

Here's the short version: It solves the same “preserved references” problem as useMemo, but for functions instead of arrays / objects.

Similar to arrays and objects, functions are compared by reference, not by value:

const functionOne = function(){ return 5; };
const functionTwo = function(){ return 5; };
console.log(functionOne === functionTwo); // false

This means that if we define a function within our components, it'll be re-generated on every single render, producing an identical-but-unique function each time.

Let's look at an example:

Code Playground

import React from 'react';

import MegaBoost from './MegaBoost';

function App() {
const [count, setCount] = React.useState(0);

function handleMegaBoost() {
setCount((currentValue) => currentValue + 1234);
}

return (
<>
Count: {count}
<button
onClick={() => {
setCount(count + 1)
}}
>
Click me!
</button>
<MegaBoost handleClick={handleMegaBoost} />
</>
);
}

export default App;
preview
console
  1. Render MegaBoost
  2. Render MegaBoost

This sandbox depicts a typical counter application, but with a special “Mega Boost” button. This button increases the count by a large amount, in case you're in a hurry and don't want to click the standard button a bunch of times.

The MegaBoost component is a pure component, thanks to React.memo (you'll find it wrapping around the default export inside MegaBoost.js). This component doesn't receive count as a prop, but it re-renders whenever count changes!

The problem here is that we're generating a brand new function on every render. If we render 3 times, we'll create 3 separate handleMegaBoost functions, breaking through the React.memo force field.

It's the same problem we saw with the boxes array in the last lesson, but instead of a twin array being passed as a prop, we have a twin function being passed as a prop.

Using what we've learned about useMemo, we could solve the problem like this:

const handleMegaBoost = React.useMemo(() => {
return function() {
setCount((currentValue) => currentValue + 1234);
}
}, []);

Instead of returning an array, we're returning a function. This function is then stored in the handleMegaBoost variable.

This works… but there's a more conventional way to do this:

const handleMegaBoost = React.useCallback(() => {
setCount((currentValue) => currentValue + 1234);
}, []);

useCallback serves the same purpose as useMemo, but it's built specifically for functions. We hand it a function directly, and it memoizes that function, threading it between renders.

Put another way, these two expressions have the same effect:

// This:
React.useCallback(function helloWorld(){}, []);
// ...Is functionally equivalent to this:
React.useMemo(() => function helloWorld(){}, []);

Essentially, useCallback is . It exists purely to make our lives a bit nicer when trying to memoize callback functions.

This stuff is hard!

When learning React, a lot of developers struggle to get comfortable with useMemo and useCallback. It's tough!

But here's the good news: you can build complex, production-ready apps without using either of these hooks!

These hooks are intended for advanced developers to optimize their applications, but React is already pretty fast out-of-the-box, and the devices that people use continue to get faster and faster.

If you've been struggling with the ideas in these “memoization” lessons, I'd encourage you to set them aside and continue on with the course. You can come back in a few months, when you're more comfortable with React.

In other words, these lessons shouldn't be "blocking". Feel free to move on for now!