import * as React from 'react'; import { useCallbackRef } from '@/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 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; } 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; } export { useControllableState };