← Back to React Course | Chapter 6: Custom Hooks & Advanced Patterns | Lesson 5 of 10

Building a useToggle Hook

A useToggle hook is like a light switch you can build once and reuse everywhere you need an on/off button.

Building a Minimal useToggle

A toggle is one of the simplest and most common pieces of UI state: a sidebar is either open or closed, a checkbox is either checked or not. useToggle wraps this pattern into a two-item return: the current boolean value and a function to flip it.

Note: Give the hook a default value parameter (e.g. useToggle(false)) so callers can choose the starting state.

Warning: Don't name the hook's return value something generic like value when using it — destructure it with a meaningful name like isOpen at the call site.

Example: Building a Minimal useToggle

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function useToggle(initial = false) {
  const [value, setValue] = React.useState(initial);
  const toggle = () => setValue(v => !v);
  return [value, toggle];
}
function App() {
  const [isOpen, toggleOpen] = useToggle(false);
  return (
    <div>
      <button onClick={toggleOpen}>Toggle Menu</button>
      {isOpen && <p>Menu is open!</p>}
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Why the Functional Updater Matters

Writing setValue(!value) reads value from the render where the function was created, which can be stale if multiple toggles happen quickly (like a fast double-click). Writing setValue(v => !v) instead always flips whatever the actual latest state is, avoiding that bug.

Note: As a habit, always use the functional updater form inside custom hooks, since you don't control how often or how fast the caller might invoke them.

Warning: Two rapid calls to setValue(!value) in the same event handler can both compute from the same stale value and cancel each other out instead of toggling twice.

Example: Why the Functional Updater Matters

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function useToggle(initial = false) {
  const [value, setValue] = React.useState(initial);
  const toggle = () => setValue(v => !v);
  return [value, toggle];
}
function App() {
  const [isOn, toggle] = useToggle(false);
  const toggleTwice = () => { toggle(); toggle(); };
  return <div><p>State: {isOn ? "ON" : "OFF"}</p><button onClick={toggleTwice}>Toggle Twice (stays same)</button></div>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Extending useToggle with Explicit Setters

Sometimes a caller needs to force the value to true or false directly, not just flip it — for example, closing a modal explicitly when clicking outside it. Returning an object with toggle, setTrue, and setFalse gives callers that extra control.

Note: Returning an object (instead of an array) is a good choice here since there are three related functions with distinct purposes.

Warning: If you add setTrue/setFalse, make sure they also use setValue(true)/setValue(false) directly rather than relying on toggle() twice, to keep behavior predictable.

Example: Extending useToggle with Explicit Setters

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function useToggle(initial = false) {
  const [value, setValue] = React.useState(initial);
  return { value, toggle: () => setValue(v => !v), setTrue: () => setValue(true), setFalse: () => setValue(false) };
}
function App() {
  const modal = useToggle(false);
  return (
    <div>
      <button onClick={modal.setTrue}>Open Modal</button>
      {modal.value && <div><p>Modal content</p><button onClick={modal.setFalse}>Close</button></div>}
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>
Common Mistakes
  1. Rebuilding the same 'const [on, setOn] = useState(false); const toggle = () => setOn(!on);' logic in every component instead of extracting it.
  2. Using setOn(!on) instead of the functional form setOn(o => !o), which can read stale state in rapid clicks.
  3. Forgetting to also expose direct setTrue/setFalse setters when the caller needs more control than just toggling.
Chapter Summary
  • useToggle is a small custom hook that manages a single boolean value with a toggle function.
  • It's a simplification built on top of useState, useful for menus, modals, and switches.
  • Using the functional state updater form avoids bugs from stale closures.
  • It's often extended to also return explicit setTrue/setFalse functions alongside toggle.
Browser Support

Available since React 16.8 (Hooks introduction) — no special browser requirements.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.