Skip to the code
cf.

useToggle

Write useToggle(initial), the default export of useToggle.js. It returns a pair, [on, toggle]: on starts as initial, or false when there's no initial.

toggle() flips on. toggle(true) and toggle(false) set it. Anything else, like the click event React passes when you write onClick={toggle}, flips it too.

toggle is the same function on every render, so components can pass it to memoized children or list it in an effect's dependencies without causing extra work.

function Lamp() {
  const [on, toggle] = useToggle();
  return <button onClick={toggle}>{on ? "On" : "Off"}</button>;
}

The Preview shows Demo.jsx, a small lamp that uses your hook.

What the tests check

  1. returns a pair: [on, toggle]
    const { result } = renderHook(() => useToggle());
    expect(result.current).toHaveLength(2);
    expect(typeof result.current[1]).toBe("function");
    
  2. starts off without an initial value
    const { result } = renderHook(() => useToggle());
    expect(result.current[0]).toBe(false);
    
  3. starts with the initial value
    const { result } = renderHook(() => useToggle(true));
    expect(result.current[0]).toBe(true);
    
  4. toggle() flips the value
    const { result } = renderHook(() => useToggle());
    act(() => result.current[1]());
    expect(result.current[0]).toBe(true);
    act(() => result.current[1]());
    expect(result.current[0]).toBe(false);
    
  5. toggle(true) and toggle(false) set the value
    const { result } = renderHook(() => useToggle());
    act(() => result.current[1](true));
    act(() => result.current[1](true));
    expect(result.current[0]).toBe(true);
    act(() => result.current[1](false));
    expect(result.current[0]).toBe(false);
    
  6. works as onClick={toggle}: a click event flips it
    function Lamp() {
      const [on, toggle] = useToggle();
      return <button onClick={toggle}>{on ? "On" : "Off"}</button>;
    }
    render(<Lamp />);
    await user.click(screen.getByRole("button"));
    expect(screen.getByRole("button")).toHaveTextContent("On");
    await user.click(screen.getByRole("button"));
    expect(screen.getByRole("button")).toHaveTextContent("Off");
    
  7. toggle is the same function on every render
    const { result, rerender } = renderHook(() => useToggle());
    const first = result.current[1];
    act(() => first());
    rerender();
    expect(result.current[1]).toBe(first);
    

Learn more

useToggle.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).