|
import * as React from 'react'; |
|
|
|
import { useCallbackRef } from '@/hooks/use-callback-ref'; |
|
|
|
|
|
|
|
|
|
|
|
type UseControllableStateParams<T> = { |
|
prop?: T | undefined; |
|
defaultProp?: T | undefined; |
|
onChange?: (state: T) => void; |
|
}; |
|
|
|
type SetStateFn<T> = (prevState?: T) => T; |
|
|
|
function useUncontrolledState<T>({ |
|
defaultProp, |
|
onChange, |
|
}: Omit<UseControllableStateParams<T>, 'prop'>) { |
|
const uncontrolledState = React.useState<T | undefined>(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<T>({ |
|
prop, |
|
defaultProp, |
|
onChange = () => {}, |
|
}: UseControllableStateParams<T>) { |
|
const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({ |
|
defaultProp, |
|
onChange, |
|
}); |
|
const isControlled = prop !== undefined; |
|
const value = isControlled ? prop : uncontrolledProp; |
|
const handleChange = useCallbackRef(onChange); |
|
|
|
const setValue: React.Dispatch<React.SetStateAction<T | undefined>> = |
|
React.useCallback( |
|
(nextValue) => { |
|
if (isControlled) { |
|
const setter = nextValue as SetStateFn<T>; |
|
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 }; |
|
|