Skip to content

Controllable State

hookstatecontrolleduncontrolled

Generic controlled/uncontrolled state hook. Manages value, defaultValue, and onChange, with a separate silent setter for restoring uncontrolled native form state.

tsx
const [value, setValue, isControlled, resetValue] = useControllableState({  value: props.value,  defaultValue: props.defaultValue ?? "",  onChange: props.onChange,});

Installation

$pnpm exec dgadd add ui/controllable-state
[Installs to]src/hooks/use-controllable-state.ts[Item]ui/controllable-state

dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.

UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS once packages are available.

Parameters

NameTypeDefaultDescription
valueT | undefinedControlled value. When provided, the component is in controlled mode.
defaultValuerequiredTInitial value for uncontrolled mode.
onChange(value: T) => voidCalled when the value changes, in both controlled and uncontrolled modes.
controlledbooleanForces controlled-mode detection when value can be undefined but the consumer still owns state.

Returns

[T, (next: T | ((prev: T) => T)) => void, boolean, (next: T) => void]Tuple of [currentValue, setValue, isControlled, resetValue]. The public setter accepts direct values and updater functions; resetValue silently restores uncontrolled internal state.
NameTypeDefaultDescription
currentValuerequiredTThe current value, whether controlled or uncontrolled.
setValuerequired(next: T | ((prev: T) => T)) => voidState setter that works in both modes. Supports functional updates.
isControlledrequiredbooleanWhether the consumer is driving the value externally.
resetValuerequired(next: T) => voidUpdates uncontrolled internal state without calling onChange. It is a no-op in controlled mode.

Examples

Basic Input

Preview

Toggle

Preview

Notes

When to Use

Use when building a component that should work both as controlled (value + onChange) and uncontrolled (defaultValue). It handles the state switching logic so the component doesn't need to.

Return Value

The isControlled flag reports external ownership. Use resetValue only for synchronization such as native form reset, where restoring an uncontrolled baseline must not emit a user change.

Source

Highlighted source loads after this disclosure opens. Browse the source repository.