useControllableState
Support both controlled and uncontrolled usage of a stateful value from a single hook.
Installation
npx shadcn@latest add @redpanda/use-controllable-statepnpm dlx shadcn@latest add @redpanda/use-controllable-stateyarn dlx shadcn@latest add @redpanda/use-controllable-statebunx shadcn@latest add @redpanda/use-controllable-statenubx shadcn@latest add @redpanda/use-controllable-stateaube dlx shadcn@latest add @redpanda/use-controllable-stateuseControllableState lets a component work in either controlled or uncontrolled mode from one hook. When a prop is provided the value is controlled and changes are reported through onChange; when it’s omitted the hook holds its own state seeded from defaultProp. This is the pattern behind value/defaultValue pairs across the registry’s form and disclosure components.
In development it warns if a component flips between controlled and uncontrolled between renders.
Usage
import { useControllableState } from "@/hooks/use-controllable-state";
function Toggle({
pressed,
defaultPressed = false,
onPressedChange,
}: {
pressed?: boolean;
defaultPressed?: boolean;
onPressedChange?: (pressed: boolean) => void;
}) {
const [value, setValue] = useControllableState({
prop: pressed,
defaultProp: defaultPressed,
onChange: onPressedChange,
caller: "Toggle",
});
return (
<button type="button" aria-pressed={value} onClick={() => setValue((p) => !p)}>
{value ? "On" : "Off"}
</button>
);
}
API
useControllableState<T>({ prop, defaultProp, onChange?, caller? })
| Option | Type | Description |
|---|---|---|
prop |
T | undefined |
The controlled value. When defined, the hook is controlled; when undefined, uncontrolled. |
defaultProp |
T |
Initial value used in uncontrolled mode. |
onChange |
(state: T) => void |
Called whenever the value changes (in both modes). |
caller |
string |
Component name used in the dev-only controlled/uncontrolled warning. |
Returns [value, setValue] — a useState-compatible tuple. setValue accepts a next value or an updater function.
Credits
Adapted from Radix UI’s useControllableState.