Redpanda UIRedpanda UI
Hooks

useControllableState

Support both controlled and uncontrolled usage of a stateful value from a single hook.

Made by Radix UI

Installation

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? })

OptionTypeDescription
propT | undefinedThe controlled value. When defined, the hook is controlled; when undefined, uncontrolled.
defaultPropTInitial value used in uncontrolled mode.
onChange(state: T) => voidCalled whenever the value changes (in both modes).
callerstringComponent 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.

On this page