{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "native-internal-hook-use-controllable-state", "type": "registry:hook", "title": "Use Controllable State", "description": "Use Controllable State from @pitsi-ui/native for native projects.", "dependencies": [], "registryDependencies": [], "files": [ { "path": "registry/native-ui/src/helpers/internal/hooks/use-controllable-state.ts", "content": "// This project uses code from WorkOS/Radix Primitives.\n// The code is licensed under the MIT License.\n// https://github.com/radix-ui/primitives/tree/main\n\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\n\n/**\n * Parameters for the useControllableState hook\n */\ntype UseControllableStateParams = {\n /** The controlled value prop */\n prop?: T | undefined;\n /** The default value for uncontrolled mode */\n defaultProp?: T | undefined;\n /** Callback fired when the value changes */\n onChange?: (state: T) => void;\n};\n\n/**\n * Function type for state setter callbacks\n */\ntype SetStateFn = (prevState?: T) => T;\n\n/**\n * A hook that supports both controlled and uncontrolled state.\n * When a value prop is provided, the component is controlled.\n * When no value prop is provided, the component manages its own state.\n *\n * @param params - Configuration object with prop, defaultProp, and onChange\n * @returns A tuple of [value, setValue] similar to useState\n */\nfunction useControllableState({\n prop,\n defaultProp,\n onChange = () => {},\n}: UseControllableStateParams) {\n const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({\n defaultProp,\n onChange,\n });\n const isControlled = prop !== undefined;\n const value = isControlled ? prop : uncontrolledProp;\n const handleChange = useCallbackRef(onChange);\n\n /**\n * When the component transitions from controlled (prop !== undefined)\n * back to uncontrolled (prop === undefined), the internal uncontrolled\n * state may hold a stale value from a previous selection. Reset it so\n * the component correctly reflects the \"no value\" state.\n */\n const prevPropRef = useRef(prop);\n useLayoutEffect(() => {\n const wasControlled = prevPropRef.current !== undefined;\n if (wasControlled && prop === undefined) {\n setUncontrolledProp(undefined);\n }\n prevPropRef.current = prop;\n }, [prop, setUncontrolledProp]);\n\n const setValue: React.Dispatch> = useCallback(\n (nextValue) => {\n if (isControlled) {\n const setter = nextValue as SetStateFn;\n const val = typeof nextValue === \"function\" ? setter(prop) : nextValue;\n if (val !== prop) handleChange(val as T);\n } else {\n setUncontrolledProp(nextValue);\n }\n },\n [isControlled, prop, setUncontrolledProp, handleChange],\n );\n\n return [value, setValue] as const;\n}\n\n/**\n * Internal hook for managing uncontrolled state with change callbacks\n */\nfunction useUncontrolledState({\n defaultProp,\n onChange,\n}: Omit, \"prop\">) {\n const uncontrolledState = useState(defaultProp);\n const [value] = uncontrolledState;\n const prevValueRef = useRef(value);\n const handleChange = useCallbackRef(onChange);\n\n useEffect(() => {\n if (prevValueRef.current !== value) {\n handleChange(value as T);\n prevValueRef.current = value;\n }\n }, [value, handleChange]);\n\n return uncontrolledState;\n}\n\n/**\n * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a\n * prop or avoid re-executing effects when passed as a dependency\n */\nfunction useCallbackRef any>(callback: T | undefined): T {\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n });\n\n // https://github.com/facebook/react/issues/19240\n return useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);\n}\n\nexport { useControllableState };\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-controllable-state.ts" } ], "categories": [ "native", "react-native", "internal-hooks" ], "meta": { "package": "@pitsi-ui/native", "packageSlug": "native-ui", "platform": "native" } }