useControlled

useControlled is a custom hook that helps you manage controlled components. It is a wrapper around useState that allows you to control the value of a component from the outside.

useControlled returns a [value, setValue] tuple that behaves like useState when the first argument is undefined (uncontrolled mode), or mirrors the provided value when it is defined (controlled mode). An optional onChange callback is invoked whenever the value changes. This pattern is essential for building components that need to work both as controlled and uncontrolled inputs.

When to Use

  • Building reusable form components (inputs, selects, sliders) that must support both controlled and uncontrolled usage
  • Wrapping third-party components that require a controlled interface while still providing a sensible uncontrolled default
  • Implementing component libraries where consumers decide whether to own the state or let the component manage it

Notes

  • No mode switching: The hook does not support switching between controlled and uncontrolled modes after initial mount. Choose one mode at creation time.
  • No function updater: Unlike useState, the setter does not accept a (prev) => next function form. Pass values directly.
  • See also useSetState for a class-component-style partial state updater.

Usage

Live Editor
function Demo() {
  const [state, setState] = useState<string>("");
  const [value, setValue] = useControlled(state, "");
  const [value1, setValue1] = useControlled(undefined, "unControlled value");

  const handleChange = (event) => {
    setState(event.target.value);
  };

  const handleChange1 = (event) => {
    setValue1(event.target.value);
  };

  return (
    <>
      <input value={value} onChange={handleChange} />
      <p>Controlled Value: {value}</p>
      <input value={value1} onChange={handleChange1} />
    </>
  );
};
Result

API

useControlledState

Returns

[T, (value: T) => void]: A tuple with the following elements:

  • The current value.
  • A function to update the value.

Arguments

ArgumentDescriptionTypeDefaultValue
valuecontrolled valueT | undefined (Required)-
defaultValuedefault valueT (Required)-
onChangecallback when value change((v: T, ...args: any[]) => void) | undefined-