{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-controllable-state",
  "title": "useControllableState",
  "description": "A custom React hook for controllable state.",
  "files": [
    {
      "path": "registry/default/hooks/use-controllable-state.ts",
      "content": "\"use client\";\r\n\r\nimport * as React from \"react\";\r\n\r\ninterface UseControllableStateParams<T> {\r\n  /** Controlled value. When defined, the hook is in controlled mode. */\r\n  value?: T;\r\n  /** Initial value for uncontrolled mode. */\r\n  defaultValue: T;\r\n  onValueChange?: (value: T) => void;\r\n}\r\n\r\n/**\r\n * Merges controlled and uncontrolled state into a single `[value, setValue]`\r\n * tuple, mirroring the pattern Radix and Base UI use internally.\r\n *\r\n * `onValueChange` fires synchronously in both modes. Functional updates are\r\n * safe in both modes: they resolve against an eagerly-advanced ref, so\r\n * multiple `setValue` calls in one event tick compose. `setValue` is\r\n * referentially stable across renders.\r\n *\r\n * The ref re-syncs from the prop at the next commit — but only if one happens.\r\n * Known constraint: a controlled parent that ignores `onValueChange` AND never\r\n * re-renders leaves the ref advanced past the real value, and a repeat\r\n * `setValue` to that same value early-returns at the `Object.is` check without\r\n * re-notifying. Acceptable — a controlled parent that never commits is already\r\n * outside the controlled contract — but it is the first thing to check when a\r\n * controlled value appears stuck.\r\n */\r\nexport function useControllableState<T>({\r\n  value,\r\n  defaultValue,\r\n  onValueChange,\r\n}: UseControllableStateParams<T>): [T, (next: T | ((prev: T) => T)) => void] {\r\n  const isControlled = value !== undefined;\r\n  const [uncontrolled, setUncontrolled] = React.useState<T>(defaultValue);\r\n  const current = isControlled ? (value as T) : uncontrolled;\r\n\r\n  const onChangeRef = React.useRef(onValueChange);\r\n  // Mirrors `current`. Re-synced after every commit; eagerly advanced by\r\n  // setValue so multiple calls in one event tick compose.\r\n  const currentRef = React.useRef(current);\r\n  React.useEffect(() => {\r\n    onChangeRef.current = onValueChange;\r\n    currentRef.current = current;\r\n  });\r\n\r\n  const setValue = React.useCallback(\r\n    (next: T | ((prev: T) => T)) => {\r\n      const prev = currentRef.current;\r\n      const resolved =\r\n        typeof next === \"function\" ? (next as (prev: T) => T)(prev) : next;\r\n      if (Object.is(resolved, prev)) return;\r\n      if (isControlled) {\r\n        // The parent owns the state, but advance the ref eagerly so a second\r\n        // functional setValue in the same tick composes on this one instead of\r\n        // clobbering it. The ref re-syncs from the prop at the next commit,\r\n        // which also corrects it if the parent rejected the change.\r\n        currentRef.current = resolved;\r\n        onChangeRef.current?.(resolved);\r\n      } else {\r\n        currentRef.current = resolved;\r\n        setUncontrolled(resolved);\r\n        onChangeRef.current?.(resolved);\r\n      }\r\n    },\r\n    [isControlled],\r\n  );\r\n\r\n  return [current, setValue];\r\n}\r\n",
      "type": "registry:hook",
      "target": "hooks/cubby-ui/use-controllable-state.ts"
    }
  ],
  "type": "registry:hook"
}