{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-controllable-state",
  "title": "useControllableState",
  "description": "A useState-shaped hook that lets one component serve both controlled (value/onChange) and uncontrolled (defaultValue) callers, with a stable setter and prop-accurate updaters.",
  "files": [
    {
      "path": "src/registry/hooks/use-controllable-state.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nexport interface UseControllableStateOptions<T> {\n  /**\n   * The controlled value. Anything other than `undefined` puts the hook into\n   * controlled mode: the returned state is always this value, and the internal\n   * state stops taking part in rendering entirely.\n   *\n   * Model \"no value\" with `null`, never `undefined` — `undefined` is the\n   * sentinel for \"this instance is uncontrolled\", the same convention a native\n   * `<input>` uses.\n   */\n  value?: T\n  /**\n   * Initial value for uncontrolled mode. Read once on mount, exactly like\n   * `useState`'s initial argument — passing a different one on a later render\n   * does not reset an already-initialized value.\n   */\n  defaultValue: T\n  /**\n   * Called with the requested next value. In controlled mode this is the *only*\n   * effect of `setState`. Skipped when the requested value is `Object.is`-equal\n   * to the current one.\n   */\n  onChange?: (value: T) => void\n}\n\nexport type UseControllableStateResult<T> = [T, (next: React.SetStateAction<T>) => void]\n\n/**\n * One state hook that makes a component work in both modes at once — the\n * `value` / `defaultValue` / `onChange` triad every well-behaved input exposes\n * — without the component's own code ever branching on which mode it is in.\n *\n * - **Controlled** (`value !== undefined`): the parent owns the value. The\n *   returned state is the prop, the internal state is not rendered, and\n *   `setState` only calls `onChange`. If the parent ignores that call — or\n *   answers it with a different value — nothing changes on screen. That is the\n *   definition of controlled, and it is what makes \"reject any rating above 3\"\n *   or \"wait for the server to confirm\" expressible without a fight.\n * - **Uncontrolled** (`value === undefined`): the hook owns the value. The\n *   internal state updates *and* `onChange` still fires, so a parent can\n *   observe changes without having to take ownership of them.\n *\n * `setState` accepts a value or an updater function, keeps one identity for the\n * lifetime of the component (safe to put in a dependency array or hand to a\n * memoized child), and always resolves updaters against the value that is\n * actually being rendered — in controlled mode that is the prop, so\n * `setState(v => !v)` flips what the user sees, not a shadow copy nobody reads.\n */\nexport function useControllableState<T>({\n  value: controlledValue,\n  defaultValue,\n  onChange,\n}: UseControllableStateOptions<T>): UseControllableStateResult<T> {\n  const isControlled = controlledValue !== undefined\n  const [internalValue, setInternalValue] = React.useState(defaultValue)\n  // The whole point: under control the internal state is dead weight, never\n  // rendered, so it can never drift into being a second source of truth.\n  const state = isControlled ? (controlledValue as T) : internalValue\n\n  // latest-ref. Everything `setState` needs is read at CALL time out of this\n  // ref instead of being captured in a closure, which is what lets `setState`\n  // keep a single identity forever. Capturing `onChange` in a dependency array\n  // instead would break on the very common `onChange={v => ...}` inline arrow:\n  // a new function every render, so a new `setState` every render, so every\n  // consumer effect depending on it re-runs on every render.\n  const latest = React.useRef({ state, isControlled, onChange })\n  // Synced in an insertion effect — the earliest commit-phase hook React\n  // offers (it runs before layout effects, before paint), so any handler that\n  // fires after this commit already reads the new value. Refs must not be\n  // written during render (`react-hooks/refs`), and a passive effect would\n  // land a beat later.\n  React.useInsertionEffect(() => {\n    latest.current = { state, isControlled, onChange }\n  })\n\n  // Uncontrolled `onChange` is emitted from a commit effect, not from inside\n  // the state updater: an updater has to stay pure (StrictMode invokes it\n  // twice, which would emit twice). Because of that, `setInternalValue` can be\n  // handed the raw `SetStateAction` and React's own queue keeps consecutive\n  // updater calls within one tick correct — `setState(n => n + 1)` twice in a\n  // row really does add 2.\n  const emittedRef = React.useRef(state)\n  React.useEffect(() => {\n    if (isControlled) {\n      // Keep it in step with the (unrendered) internal value while controlled,\n      // so a switch back to uncontrolled — warned about below, but survivable —\n      // does not look like a change and fire a phantom onChange.\n      emittedRef.current = internalValue\n      return\n    }\n    if (Object.is(emittedRef.current, internalValue)) return\n    emittedRef.current = internalValue\n    latest.current.onChange?.(internalValue)\n  }, [isControlled, internalValue])\n\n  // Flipping modes mid-life silently swaps which value wins, and the one that\n  // takes over is stale — warn in development, but never throw: a warning that\n  // crashes the page is worse than the bug it reports.\n  const modeRef = React.useRef(isControlled)\n  React.useEffect(() => {\n    if (process.env.NODE_ENV === \"production\") return\n    if (modeRef.current === isControlled) return\n    modeRef.current = isControlled\n    console.warn(\n      `useControllableState: an instance switched from ${\n        isControlled ? \"uncontrolled to controlled\" : \"controlled to uncontrolled\"\n      }. Decide once, on mount, whether \\`value\\` is passed — a component that flips between the two ` +\n        \"modes drops either the parent's value or the internal one. For an empty controlled value pass \" +\n        \"`null` rather than `undefined`.\",\n    )\n  }, [isControlled])\n\n  const setState = React.useCallback((next: React.SetStateAction<T>) => {\n    const { state: current, isControlled: controlled, onChange: notify } = latest.current\n    if (controlled) {\n      // Resolve the updater against the current PROP, never the internal\n      // state — otherwise `setState(v => v + 1)` computes from a value that\n      // has not been rendered since mount.\n      const resolved = typeof next === \"function\" ? (next as (prev: T) => T)(current) : next\n      // Ask, then stop. Whether anything actually changes is the parent's call.\n      if (!Object.is(resolved, current)) notify?.(resolved)\n      return\n    }\n    setInternalValue(next)\n  }, [])\n\n  return [state, setState]\n}\n\nexport default useControllableState\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:hook"
}