JoshWComeau

useToggle

Filed under
Snippets
on
in
May 11th, 2020.
May 2020.
import React from 'react';

export default function useToggle(initialValue = false) {
  const [value, setValue] = React.useState(initialValue);

  const toggle = React.useCallback(() => {
    setValue(v => !v);
  }, []);

  return [value, toggle];
}

Link to this headingContext

There are times when we want some React state that should always hold a boolean value, and should only allow toggling between true and false.

This is a thin wrapper around React.useState to solve for this purpose. It carries greater semantic meaning, and prevents us from accidentally setting a non-boolean value in the future.

Link to this headingUsage

const App = () => {
  const [isOn, toggleIsOn] = useToggle();

  return (
    <>
      {isOn ? 'The light is on!' : 'Hey who turned off the lights'}

      <button onClick={toggleIsOn}>
        Press me
      </button>
    </>
  )
}

The nice thing about this hook is that the toggleIsOn function can be passed straight to a click-handler; there's no need for an intermediary arrow that sets the value to the opposite of the value (and no opportunity for accidentally setting it to a non-boolean value!).

Link to this headingExplanation

This hook uses React.useCallback in order to preserve the reference to the setter function; without this wrapper, the toggle function would be recreated on every render.

The cost of creating this function is minimal, but recreating it means that it would be a new reference. This would break memoization of children elements(opens in new tab).

For example, say we have this code:

const SomeBigComponent = React.memo(({ toggleIsOn }) => {
  // Imagine lots of stuff here
});

const App = () => {
  const [isOn, toggleIsOn] = useToggle();
  const [count, setCount] = React.useState(0);

  return (
    <>
      The lights are {isOn ? 'on' : 'off'} and you've
      clicked {count} time(s).

      <button onClick={() => setCount(s => s + 1)}>
        Increment
      </button>

      <SomeBigComponent toggleIsOn={toggleIsOn} />
    </>
  )
}

A button can be clicked to increment a counter bit of state. Whenever that button is clicked, App will re-render.

Because SomeBigComponent is wrapped with React.memo, it will only re-render when its props change. And it only has one prop, the toggleIsOn function.

Without the React.useCallback in our useToggle hook, that toggleIsOn function would change on every render! This means that SomeBigComponent would re-render even when the user is incrementing the count by clicking the button.

In practice, this likely won't be a huge performance problem in most cases, but because useToggle is generic and reusable, it doesn't hurt to bake this in.

Last updated on

May 11th, 2020

# of hits