useControllableState React hook in TypeScript
v1.0.0Added in 1.0.0
A React hook for controlled and uncontrolled state: it follows a value prop when you pass one and keeps its own state when you do not.
Both steppers run on the same hook. The first keeps its own count. The second is controlled by its parent, which caps it at 5 and can reset it, so you can see who owns the value.
useControllableState is a free React hook in the React hooks and utilities category, written in TypeScript for React 19. It installs as source with npx wingo-ui@latest add use-controllable-state.
Add to a project
Requirements: React 19, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add use-controllable-stateUsage
Import useControllableState from @/hooks/use-controllable-state.
import { useControllableState } from "@/hooks/use-controllable-state"
type ToggleProps = {
pressed?: boolean
defaultPressed?: boolean
onPressedChange?: (pressed: boolean) => void
}
export function Toggle({ pressed, defaultPressed = false, onPressedChange }: ToggleProps) {
const [on, setOn] = useControllableState({
value: pressed,
defaultValue: defaultPressed,
onChange: onPressedChange,
})
return (
<button type="button" aria-pressed={on} onClick={() => setOn((prev) => !prev)}>
{on ? "On" : "Off"}
</button>
)
}API
valueT | undefinedThe controlled value. While it is defined the hook mirrors it and never stores its own; pass undefined to go uncontrolled.
defaultValue*default2TThe starting value while uncontrolled. Read once on mount, like useState.
onChange(value: T) => voidFires with the next value on every change, controlled or not. Skipped when the value did not change (Object.is).
returns[value: T, setValue: (next: T | ((prev: T) => T)) => void]The current value and a stable setter that takes a value or an updater, like useState. In controlled mode the setter only calls onChange; the parent decides.
Parts
UseControllableStateParams
typeThe hook's options: { value?: T; defaultValue: T; onChange?: (value: T) => void }.
Examples
Uncontrolled toggles
Each chip owns its pressed state from defaultPressed; no parent state at all.
Same chips, controlled
The parent passes pressed and onPressedChange, so one button can flip them all.
Expand all
Disclosure sections that open on their own, driven here by a parent Expand all button.
Dependencies
No dependencies beyond React.
Files
- hooks/use-controllable-state.ts
FAQ
Is the useControllableState hook free?
Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.
How do I install useControllableState?
Run npx wingo-ui@latest add use-controllable-state. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/use-controllable-state.json.
See also
Used in
More React hooks and utilities
Articles using this component
- React Controllable State Hook: Code and 3 Pitfalls
Next.js and Tailwind CSS10 min read