Demystifying React Hooks — useMemo

austin
7 min readDec 8, 2022

--

In this article, we will explore when and how to use React's useMemo Hook to increase your app's performance.

TL;DR

If you’re here solely to understand when to (and not to) use useMemeoand don’t want to work through the examples, I’ll spare you. According to the React Docs, much like useCallback, useMemo is an opt-in performance enhancer Hook used to create referential equality for function results. The three most common use cases are:

  1. if the calculation causes noticeable slowdown in other parts of your app
  2. if you’re passing the calculation as a prop to a child component
  3. if the calculation is used as a dependency in a Hook

If none of those 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

Buckle up and strap in. This article is on the heavier side for both theory and length.

If you'd like to follow along in your local IDE, you can find the GitHub Repo here. Otherwise, you can reference the code snippets, though you will miss out on the performance comparisons.

  • fork and clone
  • cd client
  • npm i
  • npm start

Starter Code

We'll begin with a quick overview of our starter code. In App.js, you'll find a function named "jacobsthal," two pieces of state, and a variable named “calculation”. Notice we wrapped jacobsthal in a useCallback Hook, and calculation is the returned value from calling jacobsthal.

The JSX renders both inputs and their respective values. If you need a refresher on what service the useCallback Hook provides, I'd suggest you pause here and give my useCallback article a quick read.

our App.js starter code

Our jacobsthal function is a simple, recursive function that returns the Jacobsthal Number at a given index. The specifics of the code and Jacobsthal Number don't matter for useMemo. All we care about is that it's defined within our component, hence the implementation of useCallback, and that it’s computationally expensive.

If we provide a small value to the number input, our React app behaves as expected, snappily rendering the result. However, as we increase the value of our input, while the app still provides the desired output, it takes increasingly longer to render.

Why is This Happening?

Thirty-five will be our test case because it is slow enough to be annoying but still testable. So we’ll type 35 and wait for our output to calculate.

Now start typing into the second input. See how slow it is to render? That’s because when the input changed, the entire component re-rendered, and our expensive function recalculated the output before re-rendering, even though our jacobsthal output didn’t change.

This is obviously a problem.

speed demonstration

A Quick Aside:

Junior Developers make this mistake far too often. You don’t always need useState for your forms. I'll even propose that you usually don't. If no part of your application needs to see the real-time value, like when submitting a form to an API, you should be using useRef instead. We will get into how and why in a later article.

So with the stage set and the curtains drawn, how do we resolve the issue at hand?

Memoization

Memoization is a Programming technique that stores the results of a function call, so the next time you call that function, it doesn’t have to recalculate the output. Instead, it can return the stored result, saving time complexity with recursive functions.

That’s all you need to know for now, but if you’d like a more in-depth explanation, check out this Memoization in JavaScript article by GeeksforGeeks. And at the end of this article, we will refactor our jacobsthal function to implement proper JavaScript memoization.

useMemo vs. useCallback

To sum it up, useCallback stores the definition of the function, so it doesn't unnecessarily redefine on every render. useCallback creates referential equality between instances of the function across renders.

Similarly, useMemo stores the result of the function call, so it doesn't unnecessarily recalculate on every render. useMemo creates referential equality between instances of the value across renders.

You can already see how this is helpful and leads directly to the primary purpose of useMemo. In short, the aptly named useMemo Hook is React’s built-in memoization tool.

useMemo in Action

Let’s write some code!

We’ll start by importing useMemo from 'react'.

importing useMemo from ‘react’

useMemo Syntax

You guessed it, useMemo has a similar syntactical skeleton to both useEffect and useCallback: an anonymous callback with a dependency array.

useMemo syntax comparison

As in our useCallback example, we want to cache what's returned from this Hook. So we will assign our calculation variable to the return value of useMemo, wrapping our function call in an anonymous callback.

Remember to return the result of your function call so it is accessible by useMemo!

useMemo implementation

After we save, we’ll notice a familiar warning from React. Our Hook is missing a dependency.

dependency warning

Before blindly obeying React’s warnings, let’s first think through the purpose of this dependency array and the functionality it extends to our application.

The intent of dependency arrays with React Hooks is to trigger our Hook more intentionally and specifically. When the value of the variable being “tracked” changes, the Hook knows it's time to do its thing.

In our specific case, when number changes, we want our jacobsthal function to recalculate the result.

So let’s add number to our dependency array.

dependency array added

Now that we’ve memoized our function, let’s test it out. We’ll start by inputting 35. Our calculation still takes time because our jacobsthal function is still computationally expensive. But now, when we type in the second input, our React app is again snappy and responsive. It's no longer recalculating our jacobsthal output because number has not changed.

Conclusion (kind of)

Because we memoized the results of our function, we’ve created referential equality and eliminated any unnecessary renders, making our React app more performant.

If you only came here for the React piece, thanks so much for reading, and look out for the useRef article next!

But what to do about our computationally expensive jacobsthal function? Time to refactor.

We begin by creating a previousValues parameter with a default value of an empty array. This will be our cache that we will later pass to our recursive sequence. Doing so will spare our recursive sequence from working overtime.

jacobsthal with default previous values array

Next, inside our code block, we’ll create a results variable. We will later reassign the value, so we’ll need to use our let keyword.

adding our result variable

Instead of returning our computations directly, we’ll explicitly wrap our recursive sequence in an else block and assign our return options to result.

saving returns as result rather than returning them directly

Now, after our conditionals have evaluated and assigned a value to result, we set previousValues at index n equal to our current result, then return result, thus caching this value and making it accessible as a return.

returning result

Next, the first thing our function should do is check to see if previousValues at index n exists. If it does, we return it.

check if previousValues at index n exists

Lastly, we’ll pass previousValues as an argument to our recursive sequence.

final jacobsthal function

Conclusions (for real this time)

Whew. We can now test our newly (and thoroughly) memoized component. Try 35 again. Pretty snappy, huh? So snappy, in fact, that if we enter 1026 as our input, it’s still responsive. Even calculating ‘Infinity' doesn't crash our app. And yes, useMemo is still doing its thing.

There is no lag in our other input.

If you’d like to dive deeper with useMemo, you can learn more in the official React docs.

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