import * as React from "react"; import { useCallbackRef } from "@repo/ui/hooks/use-callback-ref"; /** * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx */ type UseControllableStateParams = { prop?: T | undefined; defaultProp?: T | undefined; onChange?: (state: T) => void; }; type SetStateFn = (prevState?: T) => T; function useControllableState({ prop, defaultProp, onChange = () => {}, }: UseControllableStateParams) { const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({ defaultProp, onChange, }); const isControlled = prop !== undefined; const value = isControlled ? prop : uncontrolledProp; const handleChange = useCallbackRef(onChange); const setValue: React.Dispatch> = React.useCallback( (nextValue) => { if (isControlled) { const setter = nextValue as SetStateFn; const value = typeof nextValue === "function" ? setter(prop) : nextValue; if (value !== prop) handleChange(value as T); } else { setUncontrolledProp(nextValue); } }, [isControlled, prop, setUncontrolledProp, handleChange], ); return [value, setValue] as const; } function useUncontrolledState({ defaultProp, onChange, }: Omit, "prop">) { const uncontrolledState = React.useState(defaultProp); const [value] = uncontrolledState; const prevValueRef = React.useRef(value); const handleChange = useCallbackRef(onChange); React.useEffect(() => { if (prevValueRef.current !== value) { handleChange(value as T); prevValueRef.current = value; } }, [value, prevValueRef, handleChange]); return uncontrolledState; } export { useControllableState };