Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
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.
"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.toggle replaces () => setOpen(!open) with a cleaner APIdarkMode.value reads naturally in template expressionsnotifications.on and notifications.off provide explicit set-to-true/false callstoggle uses a functional update (v) => !v to always flip based on the current value, avoiding stale closure issues.on and off set absolute values, useful for reset buttons or event handlers that should always set a specific state.useCallback memoizes all functions so they are referentially stable across renders, safe to pass as props or use in dependency arrays.useMemo wraps the return object so consumers get a stable reference when only the functions (which are already stable) are used.| 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 |
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 };
}boolean.set function accepts boolean directly (not a union with updater function) to keep the API simple.useState(false) is fine. Fix: Reach for useToggle when you have multiple toggles or want the semantic on/off/toggle API.useMemo, the returned object creates a new reference every render, which can cause unnecessary re-renders in children. Fix: The useMemo in the recipe handles this.useToggle for local UI state, not shared state.| 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 |
useToggle provides a semantic API: toggle(), on(), off(), and set().!prev toggling in JSX.toggle() flips the current value to its opposite.on() always sets the value to true.off() always sets the value to false.set(value) sets the value to any boolean you pass.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.
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.
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).
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 };
}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.
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.
It returns UseToggleReturn, an interface with value: boolean, toggle: () => void, on: () => void, off: () => void, and set: (value: boolean) => void. No generics are needed.
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.
Reviewed by Chris St. John·Last updated Jul 16, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥