Hooks
useToggle
A boolean-state hook with a fixed toggle/setTrue/setFalse/set action set that keeps a stable reference across renders.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/use-toggle.jsonPrompt
Build a React + TypeScript "useToggle" hook (no dependencies beyond React).
Contract
- `useToggle(initial?: boolean): [value: boolean, actions: { toggle: () => void;
setTrue: () => void; setFalse: () => void; set: (next: boolean) => void }]`.
- `initial` defaults to `false`, captured once on mount — same semantics as
`useState`'s initial argument (a different literal on a later render doesn't
reset an already-initialized value).
- Returns a 2-tuple `[value, actions]`, not a flat object — mirrors `useState`'s
call shape so it reads like a drop-in boolean `useState` with a few extra
named actions bolted on.
Behavior
- `toggle()` flips `value` via a functional update (`v => !v`), so it never
reads a `value` captured in a stale closure — two toggles queued in the same
tick (e.g. two batched event handlers) both apply correctly instead of one
silently clobbering the other's read.
- `setTrue()` / `setFalse()` jump straight to a known value regardless of the
current one — safe to wire to two independent triggers (an "open" button and
a "close" button) without them ever racing into an inconsistent state the
way two blind `toggle()` calls could.
- `set(next)` sets an arbitrary boolean directly, for callers that already
computed the target value.
- Every action is wrapped in `useCallback` with an empty dependency array
(the underlying `useState` setter is already stable, so none of the actions
need to close over `value`), then grouped into one `actions` object with
`useMemo`. Both the individual actions and the `actions` object itself keep
the same reference across every re-render of the calling component.
Rendering & styling
- The hook renders nothing and touches no DOM. Consumers own all UI — style
open/closed states with semantic tokens (`bg-primary`, `bg-muted`,
`bg-card`, `text-muted-foreground`, `border`) and respect
`prefers-reduced-motion` for any transition tied to the toggle.
Customization levers
- Rename the hook/return shape to a `useDisclosure`-style API
(`{ isOpen, onOpen, onClose, onToggle }`) if that naming convention is
preferred elsewhere in the codebase — the implementation (functional
update + stable callbacks) doesn't change, only the labels.
- Controlled mode — accept optional `value`/`onChange` props and, when
present, call `onChange` instead of the internal setter (mirroring a
controlled/uncontrolled native input) if a parent needs to own the boolean;
deliberately left out by default to keep the hook a plain drop-in
`useState` replacement.Concepts
- Functional update, not read-then-write —
toggle()callssetValue(v => !v), so it never depends on thevaluecaptured in whatever closure it was defined in; two toggles queued in the same tick always both apply instead of one overwriting the other's stale read. - Fixed action set beats an ad-hoc setter — instead of exposing a raw
setValue, the hook commits totoggle/setTrue/setFalse/set, so call sites read as intent ("open the drawer") rather than a boolean flip a reader has to reverse-engineer. - Stable callback identity — every action (and the
actionsobject) is memoized with an empty dependency array, so its reference never changes between renders — unlike an inlineconst toggle = () => setValue(!value), which is a new function every render and would force any effect/useCallback/memoized child depending on it to re-run or re-render needlessly. setTrue/setFalseare idempotent,toggleisn't — callingsetFalse()twice in a row is a no-op the second time; callingtoggle()twice flips back to where you started. Prefer the named actions whenever two independent triggers should never race into an inconsistent state.- Multi-instance independence — the hook holds no module-level state, so a list of accordion panels or drawers each call
useToggle()on their own and never step on each other's value.