import { useCallback, useMemo, useState, useRef, useEffect } from 'react'; type ReturnType = [T, (value: T, ...args: any[]) => void]; type Args = { value?: T; defaultValue?: T; onChange?: (value: T, ...args: any[]) => any; disabled?: boolean; manipulateValue?: (value: T | undefined, prevValue: T | undefined) => T; }; const defaultManipulateValue = (val: any) => val; const useControlledValue = ({ value: valueProp, defaultValue, disabled = false, onChange, manipulateValue = defaultManipulateValue, }: Args): ReturnType => { const value = useMemo( () => valueProp !== undefined ? manipulateValue(valueProp, undefined) : manipulateValue(defaultValue, undefined), [defaultValue, manipulateValue, valueProp], ); const hasWarnedRef = useRef(false); const isUncontrolled = useRef(valueProp).current === undefined; const [uncontrolledValue, setValue] = useState(value); const updateValue = useCallback( (val: T, ...rest: any[]) => { if (disabled) return; if (isUncontrolled) { setValue(manipulateValue(val, uncontrolledValue)); } onChange?.( manipulateValue(val, isUncontrolled ? uncontrolledValue : valueProp), ...rest, ); }, [disabled, isUncontrolled, manipulateValue, onChange, uncontrolledValue, valueProp], ); useEffect(() => { if (hasWarnedRef.current) return; hasWarnedRef.current = true; if (valueProp !== undefined && isUncontrolled) { console.warn( 'Trying to change the value from uncontrolled to controlled can lead to inconsistencies', ); } }, [isUncontrolled, valueProp]); return useMemo( () => [isUncontrolled ? uncontrolledValue : value, updateValue], [isUncontrolled, uncontrolledValue, updateValue, value], ); }; export default useControlledValue;