June 08, 2021
0 strokes bestowed

`useMemo` and Stable Values

edit

The most common pattern I use when writing custom React hooks is to return a tuple of [state, handlers]. state is the value held by the hook, and handlers is an object with methods that update the state. The way I write the handlers object typically raises some questions. I'll show you an example from a basic useSwitch hook.

function useSwitch(initialState = false) {
  const [state, setState] = React.useState(initialState)

  // PAY ATTENTION HERE
  const handlers = React.useMemo(
    () => ({
      on: () => {
        setState(true)
      },
      off: () => {
        setState(false)
      },
      toggle: () => {
        setState(s => !s)
      },
      reset: () => {
        setState(initialState)
      },
    }),
    [initialState]
  )

  return [state, handlers]
}

The typical question I get, and why I'm writing this post, is "Why did you use useMemo instead of several useCallbacks?"

The answer has two parts, but both are straightforward:

  • useMemo is the simplest way I know to create a stable value
  • A handlers object of methods is the nicest API I can provide my hook users

Creating stable values

useMemo returns a memoized value. Learn about memoization here if you need to first. This means that as long as the dependencies don't change, useMemo will return the cached value from when it was previously computed. It is made clear in the docs that this is not a guarantee, but for all intents and purposes, we can treat it like it is one. Therefore, we can use useMemo to eliminate value changes that may cause unnecessary renders downstream. I have found useMemo especially useful for values stored by reference: arrays, objects, sets, etc, and hence why I use it for my handlers object.

Nice APIs

Often, the abbreviation API is reserved for the interface for getting items from a database or a service, but often I think about what I'm returning from a function as an API as well. I think this is the best way to think about custom React hooks. Ask yourself, "What API am I giving users of this?" as you design it.

When it comes to custom hooks, the API pattern of a tuple of [state, handlers] is very simple, yet flexible for the user. Perhaps my user only needs one method from handlers. Or needs to rename a method to be more semantically accurate for their purposes:

function Door() {
  const [isOpen, { toggle }] = useSwitch(false)

  return (
    <div>
      <p>The door is {isOpen ? 'open' : 'closed'}.</p>
      <div>
        <Button onClick={toggle}>Toggle door</Button>
      </div>
    </div>
  )
}

Imagine if I did not use an object for my handlers? What if we wrote it as a collection of useCallbacks instead?

function useSwitch(initialState = false) {
  const [state, setState] = React.useState(initialState)

  const on = React.useCallback(() => {
    setState(true)
  }, [])

  const off = React.useCallback(() => {
    setState(false)
  }, [])

  const toggle = React.useCallback(() => {
    setState(s => !s)
  }, [])

  const reset = React.useCallback(() => {
    setState(initialState)
  }, [initialState])

  return [state /* WHAT DO I DO HERE? */]
}

Do I return each callback as a positional item in the tuple? No! That would be a pain in the ass.

function useSwitch(initialState = false) {
  // ... all the code from before
  return [state, on, off, toggle, reset]
}

function Door() {
  // you can skip items while array destructuring by not assigning the value
  // at that index a variable name
  const [isOpen, , , toggle] = useSwitch(false)

  // ... the rest of the code
}

So we're back to putting them in an object:

function useSwitch(initialState = false) {
  // ... all the code from before
  return [state, { on, off, toggle, reset }]
}

But that object is getting recreated every render. Might as well use useMemo and return a stable object. They're practically never going to change anyways!

Summary

useMemo is a way of creating stable values. It can be useful for values stored by reference, like an object of methods. Use it to optimize the performance of downstream consumers of that value. That's it.


Finished reading?

Here are a few options for what to do next.

Like
Liked the post? Click the beard up to 50 times to show it
Share
Sharing this post on Twitter & elsewhere is a great way to help me out
Support
Was this post valuable to you? Make a donation to show it
Make a Donation
Kofi logo

Related Posts:
Memoization: What, Why, and HowUsing `React.memo` to Avoid Unnecessary Rerenders
Tags
React

Let's talk some more about JavaScript, React, and software engineering.

I write a newsletter to share my thoughts and the projects I'm working on. I would love for you to join the conversation. You can unsubscribe at any time.

Just Enough Functional Programming Logo
Just Enough Functional Programming

Check out my courses!

Liked the post? You might like my video courses, too. Click the button to view this course or go to Courses for more information.
View on egghead.io
Kyle Shevlin's face, which is mostly a beard with eyes
Kyle Shevlin is a software engineer who specializes in JavaScript, React and front end web development.