June 08, 2021

`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.


Liked the post?
Give the author a dopamine boost with a few "beard strokes". Click the beard up to 50 times to show your appreciation.
Need help with your software problems?

My team and I are ready to help you. Hire Agathist to build your next great project or to improve one of your existing ones.

Get in touch
Kyle Shevlin's face, which is mostly a beard with eyes

Kyle Shevlin is the founder & lead software engineer of Agathist, a software development firm with a mission to build good software with good people.

Agathist
Good software by good people.
Visit https://agath.ist to learn more
Logo for Array.reduce()
Array.reduce()
Check out my courses!
If you enjoy my posts, you might enjoy my courses, too. Click the button to view the course or go to Courses for more information.
Sign up for my newsletter
Let's chat some more about TypeScript, React, and frontend web development. Unsubscribe at any time.