Hooks
useControllableState
Support both controlled and uncontrolled usage of a stateful value from a single hook.
Made by Radix UIInstallation
useControllableState 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.
Built by malinskibeniamin. The source code is available on GitHub.