# useControllableState

> A React hook for controlled and uncontrolled state: it follows a value prop when you pass one and keeps its own state when you do not.

- Type: React hook (Hooks & Utilities)
- Access: Free (the source is open to everyone)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add use-controllable-state`
- Docs: https://wingo-ui.com/components/use-controllable-state

## Interaction

Both steppers run on the same hook. The first keeps its own count. The second is controlled by its parent, which caps it at 5 and can reset it, so you can see who owns the value.

## Installation

```bash
npx wingo-ui@latest add use-controllable-state
```

The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add https://wingo-ui.com/r/use-controllable-state.json`

Files:

- `hooks/use-controllable-state.ts`

## Usage

```tsx
import { useControllableState } from "@/hooks/use-controllable-state"

type ToggleProps = {
  pressed?: boolean
  defaultPressed?: boolean
  onPressedChange?: (pressed: boolean) => void
}

export function Toggle({ pressed, defaultPressed = false, onPressedChange }: ToggleProps) {
  const [on, setOn] = useControllableState({
    value: pressed,
    defaultValue: defaultPressed,
    onChange: onPressedChange,
  })

  return (
    <button type="button" aria-pressed={on} onClick={() => setOn((prev) => !prev)}>
      {on ? "On" : "Off"}
    </button>
  )
}
```

## API

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `value?` | `T \| undefined` |  | The controlled value. While it is defined the hook mirrors it and never stores its own; pass undefined to go uncontrolled. |
| `defaultValue` | `T` | `2` | The starting value while uncontrolled. Read once on mount, like useState. |
| `onChange?` | `(value: T) => void` |  | Fires with the next value on every change, controlled or not. Skipped when the value did not change (Object.is). |
| `returns?` | `[value: T, setValue: (next: T \| ((prev: T) => T)) => void]` |  | The current value and a stable setter that takes a value or an updater, like useState. In controlled mode the setter only calls onChange; the parent decides. |

## Parts

### UseControllableStateParams (type)

The hook's options: { value?: T; defaultValue: T; onChange?: (value: T) => void }.

## Examples

- **Uncontrolled toggles**: Each chip owns its pressed state from defaultPressed; no parent state at all.
- **Same chips, controlled**: The parent passes pressed and onPressedChange, so one button can flip them all.
- **Expand all**: Disclosure sections that open on their own, driven here by a parent Expand all button.

## See also

- [usePersistedState](https://wingo-ui.com/components/use-persisted-state) ([Markdown](https://wingo-ui.com/components/use-persisted-state.md))
- [useSelection](https://wingo-ui.com/components/use-selection) ([Markdown](https://wingo-ui.com/components/use-selection.md))
- [Quantity stepper](https://wingo-ui.com/components/quantity-stepper) ([Markdown](https://wingo-ui.com/components/quantity-stepper.md))
- [Toggle](https://wingo-ui.com/components/toggle) ([Markdown](https://wingo-ui.com/components/toggle.md))
- [Collapsible](https://wingo-ui.com/components/collapsible) ([Markdown](https://wingo-ui.com/components/collapsible.md))

---

Canonical: https://wingo-ui.com/components/use-controllable-state
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
