useToggle — Simple boolean toggle with on, off, and toggle controls
Recipe
import { useState, useCallback, useMemo } from "react";
interface UseToggleReturn {
/** Current boolean value */
value: boolean;
/** Flip the value */
toggle: () => void;
/** Set to true */
on: () => void;
/** Set to false */
off: () => void;
/** Set to a specific value */
set: (value: boolean) => void;
}
function useToggle(initialValue: boolean = false): UseToggleReturn {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue((v) => !v), []);
const on = useCallback(() => setValue(true), []);
const off = useCallback(() => setValue(false), []);
return useMemo(
() => ({ value, toggle, on, off, set: setValue }),
[value, toggle, on, off]
);
}When to reach for this: You have a boolean state (modal open, sidebar visible, dark mode, feature flag) and want a cleaner API than raw useState with !prev toggling scattered through your JSX.
Working Example
"use client";
function Sidebar() {
const sidebar = useToggle(false);
return (
<div style={{ display: "flex" }}>
<aside
style={{
width: sidebar.value ? 250 : 0,
overflow: "hidden",
transition: "width 0.3s",
background: "#f5f5f5",
borderRight: "1px solid #e0e0e0",
}}
>
<nav style={{ padding: 16, whiteSpace: "nowrap" }}>
<ul style={{ listStyle: "none", padding: 0 }}>
<li>Dashboard</li>
<li>Settings</li>
<li>Profile</li>
</ul>
</nav>
</aside>
<main style={{ flex: 1, padding: 16 }}>
<button onClick={sidebar.toggle}>
{sidebar.value ? "Close" : "Open"} Sidebar
</button>
<p>Main content here</p>
</main>
</div>
);
}
function DarkModeToggle() {
const darkMode = useToggle(false);
return (
<div
style={{
background: darkMode.value ? "#1a1a2e" : "#ffffff",
color: darkMode.value ? "#e0e0e0" : "#333",
padding: 24,
transition: "all 0.3s",
}}
>
<label style={{ display: "flex", alignItems: "center", gap: 8 }}>
<input
type="checkbox"
checked={darkMode.value}
onChange={darkMode.toggle}
/>
Dark Mode
</label>
</div>
);
}
function FeatureFlags() {
const betaFeature = useToggle(false);
const notifications = useToggle(true);
return (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<label>
<input
type="checkbox"
checked={betaFeature.value}
onChange={betaFeature.toggle}
/>
Beta Features
</label>
<label>
<input
type="checkbox"
checked={notifications.value}
onChange={notifications.toggle}
/>
Notifications
</label>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={notifications.on}>Enable All</button>
<button onClick={notifications.off}>Disable All</button>
</div>
</div>
);
}What this demonstrates:
sidebar.togglereplaces() => setOpen(!open)with a cleaner APIdarkMode.valuereads naturally in template expressionsnotifications.onandnotifications.offprovide explicit set-to-true/false calls- Multiple toggles are independent and can be composed freely
Deep Dive
How It Works
toggleuses a functional update(v) => !vto always flip based on the current value, avoiding stale closure issues.onandoffset absolute values, useful for reset buttons or event handlers that should always set a specific state.useCallbackmemoizes all functions so they are referentially stable across renders, safe to pass as props or use in dependency arrays.useMemowraps the return object so consumers get a stable reference when only the functions (which are already stable) are used.
Parameters & Return Values
| Parameter | Type | Default | Description |
|---|---|---|---|
initialValue | boolean | false | Starting value |
| Return | Type | Description |
|---|---|---|
value | boolean | Current state |
toggle | () => void | Flip the value |
on | () => void | Set to true |
off | () => void | Set to false |
set | (value: boolean) => void | Set to any boolean |
Variations
Tuple return: For a more useState-like API, return a tuple:
function useToggle(initial = false): [boolean, () => void] {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue((v) => !v), []);
return [value, toggle];
}
// Usage
const [isOpen, toggleOpen] = useToggle();With reducer: For complex toggle logic with action history:
function useToggle(initial = false) {
const [value, dispatch] = useReducer(
(state: boolean, action: "toggle" | "on" | "off") => {
switch (action) {
case "toggle": return !state;
case "on": return true;
case "off": return false;
}
},
initial
);
return { value, dispatch };
}Persistent toggle: Combine with useLocalStorage for toggles that survive refresh:
function usePersistentToggle(key: string, initial = false) {
const [value, setValue] = useLocalStorage(key, initial);
const toggle = useCallback(() => setValue((v) => !v), [setValue]);
const on = useCallback(() => setValue(true), [setValue]);
const off = useCallback(() => setValue(false), [setValue]);
return { value, toggle, on, off };
}TypeScript Notes
- The return type is a named interface, making it easy to pass the toggle object as a prop.
- No generics needed since the value is always
boolean. - The
setfunction acceptsbooleandirectly (not a union with updater function) to keep the API simple.
Gotchas
- Over-engineering — For a single toggle used once, plain
useState(false)is fine. Fix: Reach foruseTogglewhen you have multiple toggles or want the semanticon/off/toggleAPI. - Object identity — Without
useMemo, the returned object creates a new reference every render, which can cause unnecessary re-renders in children. Fix: TheuseMemoin the recipe handles this. - Controlled vs uncontrolled — This hook owns its own state. If the parent needs to control the value, pass the state down as props instead. Fix: Use
useTogglefor local UI state, not shared state.
Alternatives
| Package | Hook Name | Notes |
|---|---|---|
usehooks-ts | useToggle | Tuple return [value, toggle] |
@uidotdev/usehooks | useToggle | Minimal, tuple return |
ahooks | useToggle | Supports non-boolean toggle between two values |
react-use | useToggle | Simple boolean toggle |
FAQs
Why use useToggle instead of plain useState(false)?
useToggleprovides a semantic API:toggle(),on(),off(), andset().- It eliminates scattered
!prevtoggling in JSX. - It is most useful when you have multiple toggles or want clearer intent in event handlers.
What is the difference between toggle(), on(), off(), and set()?
toggle()flips the current value to its opposite.on()always sets the value totrue.off()always sets the value tofalse.set(value)sets the value to any boolean you pass.
Why does the hook wrap the return object in useMemo?
Without useMemo, a new object reference is created every render. This can cause unnecessary re-renders in child components that receive the toggle object as a prop. useMemo keeps the reference stable when only the functions (already memoized) are used.
Why does toggle() use a functional update (v) => !v instead of !value?
The functional update reads the current state at the time of execution, not at the time the function was created. This avoids stale closure bugs when toggle() is called from event handlers or effects.
Can I use the tuple return pattern instead of the object pattern?
Yes. The Variations section shows a tuple variant:
const [isOpen, toggleOpen] = useToggle();Use the tuple for simple cases and the object for multiple controls (on, off, set).
How do I make a toggle persist across page refreshes?
Combine with useLocalStorage:
function usePersistentToggle(key: string, initial = false) {
const [value, setValue] = useLocalStorage(key, initial);
const toggle = useCallback(() => setValue((v) => !v), [setValue]);
return { value, toggle };
}Gotcha: I passed the toggle object as a prop and my child re-renders every time. Why?
If you destructure and re-create the object in the parent, the reference changes. Pass the useMemo-wrapped object directly, or pass individual stable functions (toggle, on, off) as separate props.
Gotcha: I used useToggle for state that the parent also needs to control. Is that correct?
No. useToggle owns its own state (uncontrolled). If the parent needs to control the value, pass the boolean as a prop and use callbacks. Use useToggle only for local UI state.
What is the TypeScript return type of useToggle?
It returns UseToggleReturn, an interface with value: boolean, toggle: () => void, on: () => void, off: () => void, and set: (value: boolean) => void. No generics are needed.
Can I use useReducer instead of useState for the toggle?
Yes. The Variations section shows a reducer-based variant that dispatches string actions like "toggle", "on", and "off". This is useful for complex toggle logic or action history tracking.
Related
- useLocalStorage — persist toggle state
- useKeyboardShortcut — toggle with a hotkey
- useClickOutside — close on outside click