{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "use-controllable-state", "type": "registry:hook", "title": "Controllable State Hook", "files": [ { "path": "src/hooks/use-layout-effect.tsx", "content": "// This code comes from https://github.com/radix-ui/primitives/blob/main/packages/react/use-layout-effect/src/use-layout-effect.tsx\nimport * as React from \"react\";\n\n/**\n * On the server, React emits a warning when calling `useLayoutEffect`.\n * This is because neither `useLayoutEffect` nor `useEffect` run on the server.\n * We use this safe version which suppresses the warning by replacing it with a noop on the server.\n *\n * See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect\n */\nconst useLayoutEffect = globalThis?.document ? React.useLayoutEffect : () => {};\n\nexport { useLayoutEffect };\n", "type": "registry:hook" }, { "path": "src/hooks/use-controllable-state.ts", "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n// This code comes from https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/use-controllable-state.tsx\n\nimport * as React from \"react\";\n\nimport { useLayoutEffect } from \"@/hooks/use-layout-effect\";\n\n// Prevent bundlers from trying to optimize the import\nconst useInsertionEffect: typeof useLayoutEffect =\n (React as any)[\" useInsertionEffect \".trim().toString()] || useLayoutEffect;\n\ntype ChangeHandler = (state: T) => void;\ntype SetStateFn = React.Dispatch>;\n\ninterface UseControllableStateParams {\n prop?: T | undefined;\n defaultProp: T;\n onChange?: ChangeHandler;\n caller?: string;\n}\n\nexport function useControllableState({\n prop,\n defaultProp,\n onChange = () => {},\n caller,\n}: UseControllableStateParams): [T, SetStateFn] {\n const [uncontrolledProp, setUncontrolledProp, onChangeRef] =\n useUncontrolledState({\n defaultProp,\n onChange,\n });\n const isControlled = prop !== undefined;\n const value = isControlled ? prop : uncontrolledProp;\n\n // OK to disable conditionally calling hooks here because they will always run\n // consistently in the same environment. Bundlers should be able to remove the\n // code block entirely in production.\n /* eslint-disable react-hooks/rules-of-hooks */\n if (process.env.NODE_ENV !== \"production\") {\n const isControlledRef = React.useRef(prop !== undefined);\n React.useEffect(() => {\n const wasControlled = isControlledRef.current;\n if (wasControlled !== isControlled) {\n const from = wasControlled ? \"controlled\" : \"uncontrolled\";\n const to = isControlled ? \"controlled\" : \"uncontrolled\";\n console.warn(\n `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n );\n }\n isControlledRef.current = isControlled;\n }, [isControlled, caller]);\n }\n /* eslint-enable react-hooks/rules-of-hooks */\n\n const setValue = React.useCallback>(\n (nextValue) => {\n if (isControlled) {\n const value = isFunction(nextValue) ? nextValue(prop) : nextValue;\n if (value !== prop) {\n onChangeRef.current?.(value);\n }\n } else {\n setUncontrolledProp(nextValue);\n }\n },\n [isControlled, prop, setUncontrolledProp, onChangeRef]\n );\n\n return [value, setValue];\n}\n\nfunction useUncontrolledState({\n defaultProp,\n onChange,\n}: Omit, \"prop\">): [\n Value: T,\n setValue: React.Dispatch>,\n OnChangeRef: React.RefObject | undefined>,\n] {\n const [value, setValue] = React.useState(defaultProp);\n const prevValueRef = React.useRef(value);\n\n const onChangeRef = React.useRef(onChange);\n useInsertionEffect(() => {\n onChangeRef.current = onChange;\n }, [onChange]);\n\n React.useEffect(() => {\n if (prevValueRef.current !== value) {\n onChangeRef.current?.(value);\n prevValueRef.current = value;\n }\n }, [value, prevValueRef]);\n\n return [value, setValue, onChangeRef];\n}\n\nfunction isFunction(value: unknown): value is (...args: any[]) => any {\n return typeof value === \"function\";\n}\n", "type": "registry:hook" } ] }