import { useCallback , useEffect , useMemo , useRef } from 'react' ; import { useHistory , useLocation } from 'react-router-dom' ; import buildParamQuery from '../../utils/buildParamQuery' ; import encodeParam from '../../utils/encodeParam' ; type SetValueCallback < T > = ( val : T ) => T | null ; type SetValueFn < T > = ( value : T | null | SetValueCallback < T > , method ? : 'push' | 'replace' ) => void ; export interface UrlParam < T > { name : string ; defaultValue : T ; initialValue ? : T ; decode : ( value : string ) => NonNullable < T > ; encode : ( value : NonNullable < T > ) => string ; } const useParam = < T > ( urlParam ) : [ T , SetValueFn < T > ] => { const history = useHistory ( ) ; const location = useLocation ( ) ; const params = new URLSearchParams ( location . search ) ; const rawValue = params . get ( urlParam . name ) ; const state = useMemo ( ( ) => ( rawValue ? urlParam . decode ( rawValue ) : urlParam . defaultValue ) , [ rawValue ] ) ; const stateRef = useRef ( state ) ; const locationRef = useRef ( location ) ; useEffect ( ( ) => { stateRef . current = state ; } , [ stateRef , state ] ) ; useEffect ( ( ) => { locationRef . current = location ; } , [ locationRef , location ] ) ; const setValue = useCallback < SetValueFn < T > > ( ( valueOrFn , method = 'push' ) => { const value = valueOrFn instanceof Function ? valueOrFn ( stateRef . current ) : valueOrFn ; const newValue = value !== null ? encodeParam ( urlParam , value ) : null ; const newParams = buildParamQuery ( urlParam , newValue ) ; return history [ method ] ( { ... locationRef . current , search : newParams . toString ( ) } ) ; } , [ ] ) ; return [ state , setValue ] ; } ; export default useParam ;