Demystifying React Hooks — useCallback

In this article, we will explore when and how to use React’s useCallback hook and a mistake made by most Junior Developers.

austin
7 min readDec 2, 2022

TL;DR

If you’re here for a quick definition and don’t want to traverse through the examples, I’ll spare you. According to the React Docs,useCallback is an opt-in performance enhancer Hook used to create referential equality for function definitions. The two most common use cases are:

  1. when you’re passing a function as a prop to a child component
  2. if the function is used as a dependency in a Hook.

If neither of those two conditions is being met, you’re likely wasting time, lines, and sacrificing readability (and memory) by proactively “optimizing” your app.

With that out of the way, Let’s get started.

Getting Started

If you’d like to follow along in your local IDE, you can find the GitHub Repo here.

  • clone
  • cd client
  • npm i
  • npm start

Referential Equality

Referential Equality is a foundational concept in both JavaScript and Computer Science as a whole. So let’s start with a demonstration of it in action.

I’ve embedded screenshots throughout the article for ease of use on mobile. If you’re on desktop and have cloned it down, run referentialEquality.js to observe the output or just play with the JSFiddle snippet embedded below.

When evaluating whether the integer 1 is strictly equal to the integer 1, the console prints true. This is because, well… the integer 1 is strictly equal to the integer 1.

the integers are strictly equal

We see the same result when evaluating two strings.

the strings are strictly equal

Obviously, this will always be the case for two primitive data types of the same value.

Now, what about data structures? For example, two object literals with the same key/value pairs? What about empty object literals?

the objects are not strictly equal

Why would this print false? When comparing whether these two object literals are strictly equal, JavaScript uses their respective memory addresses.

In other words, these two objects may contain the same values, but they’re not referencing the same object. They look the same but occupy two different spaces in memory.

The same applies whether you’re comparing two object literals, two array literals, or two functions!

the arrays are not strictly equal

To demonstrate this further, we will define a function func, which returns an anonymous function that, in turn, returns something else (like a JSX element).

creating a pretend functional component

We will then assign two different functions, firstRender and secondRender, equal to the value returned by func.

assigning our function definitions

Think of func as your React functional component, while firstRender is a function inside of it on the first render, and secondRender is a function inside of it on the second render.

Even though firstRender and secondRender return the same value and are assigned from the same definition, they do not have referential equality. As a result, every time the component renders, it redefines this function.

our two functions are not strictly equal

Unfortunately, in JavaScript, it isn’t easy to print these memory addresses like in Python, but for a slightly more in-depth explanation of reference vs. value, take a look at this article from freeCodeCamp.

This topic can get dense, and you don’t need to teach a class on it tonight. So, for now, just remember:

  • primitive data type === primitive data type
  • data structure !== data structure.

With referential equality out of the way, let’s dive into our React code and see why this is relevant.

Starter Code

After we spin up our app, open the BookDetails.jsx component and re-save. The first thing we may notice in our React dev server is a common WARNING that young developers tend to ignore. As you hit the workforce and start writing code for production, your linters will be even more strict than what’s built into create-react-app. WARNINGS will turn to ERRORS, and some linters won’t allow you to push before you address these ERRORS.

So rather than ignore it, let’s figure out how to treat it.

React’s proposed solutions

NOTE: you may first need to re-save BookDetails.jsx to create this WARNING

If we dig into the React Docs, we can decode the semi-confusing proposed solutions to this WARNING as follows:

1. Include the function definition inside of the useEffect .

  • We cannot call this function elsewhere unless we redefine it.

2. Remove the dependency array.

  • This will trigger the useEffect every time state or props change, sometimes causing an infinite re-render. And in our case, since we’re calling our state setter function after an API call, it could overload our API with infinite endpoint requests.

3. Remove the function call from the useEffect.

  • The function won’t get called.

4. Include the function in the dependency array.

  • The first time the component renders, it will define our function, which will trigger the useEffect, which will cause the component to re-render, which will redefine the function, which will trigger the useEffect, which will cause the component to re-render, which will redefine the function…

So what’s a developer to do?

The simplest and preferred solution would be to ‘include it,’ that is, move the getBookDetails function definition inside the useEffect. This adheres to an Object-Oriented Programming principal known as Encapsulation.

But let’s say we know we need to call the function elsewhere. Should we redefine it later? That’s not very DRY of us.

Let’s change our dependency array to include our function reference. Your useEffect should now look like this.

including our function in the dependency array

And getBookDetails remains defined above the useEffect.

our function’s original definition

Now we have a new WARNING

defining our function outside of the useEffect while including it in the dependency array will cause an infinite re-render

Enter the useCallback Hook

In short, the useCallback hook allows you to cache, or ‘memoize,’ a function between re-renders of your component. It performs a similar task to useMemo, the nuances of which we will get into in a different article.

If the nitty-gritty of this interests you, you can read more in the React docs.

Please notice their warning:

You should only rely on useCallback as a performance optimization. If your code doesn’t work without it, find the underlying problem and fix it first. Then you may add useCallback to improve performance.

useCallback Syntax

useCallback’s syntax is very similar to the useEffect’s syntax which we already know. Look at the skeletons of each.

the two hooks have identical syntactical skeletons

The slight difference is with useEffect, we tell the anonymous function to execute our function while with useCallback, we assign the return value to a reference to be called elsewhere.

Using useCallback

First, we will import useCallback from 'react'. Rather than adding a new line, it’s best to destructure it along with our other imports.

items destructured from the same package are best imported on the same line

Now we can assign getBookDetails to the value returned from a useCallback function call.

assign useCallback’s return to our reference

Then we add all the syntax for useCallback. Remember your dependency array!

fill in useCallback’s skeleton

In our example, we need async before our parameters.

add async

And finally, we add the logic of our function into the code block.

add our logic to useCallback’s code block

Once we save, we get… another WARNING .

useCallback’s return depends on the value of id

Why should our dependency array track the id variable?

Let’s think through this.

  • If the value of id changes, getBookDetails needs to hit a different endpoint, so React should redefine it. The definition of getBookDetails literally depends on the value of id .

After we add id to our dependency array, our finished getBookDetails and useEffect functions should look like this. Look closely at the differences between the way we implement the two hooks.

finished versions of both hooks

And finally, that’s it! We see green in our React dev server. A happy linter is a happy Senior Developer. And a happy Senior Developer is a happy you!

I’m always looking for new friends and colleagues. If you found this article helpful and would like to connect, you can find me at any of my homes on the web.

GitHub | Twitter | LinkedIn | Website

Resources

--

--

austin
austin

Written by austin

a cameraman turned software developer

No responses yet