import * as React from 'react' ; import { QueryParamConfig , StringParam } from 'serialize-query-params' ; import { getSSRSafeSearchString , useUpdateRefIfShallowNew } from './helpers' ; import { useLocationContext } from './LocationProvider' ; import { sharedMemoizedQueryParser } from './memoizedQueryParser' ; import shallowEqual from './shallowEqual' ; import { UrlUpdateType } from './types' ; type NewValueType < D > = D | ( ( latestValue : D ) => D ) ; /**
 * Helper to get the latest decoded value with smart caching.
 * Abstracted into its own function to allow re-use in a functional setter (#26)
 */ function getLatestDecodedValue < D , D2 = D > ( location , name , paramConfig , paramConfigRef , encodedValueCacheRef , decodedValueCacheRef ) { const hasNewParamConfig = ! shallowEqual ( paramConfigRef . current , paramConfig ) ; const isValueEqual = paramConfig . equals ?? shallowEqual ; const parsedQuery = sharedMemoizedQueryParser ( getSSRSafeSearchString ( location ) ) ; const hasNewEncodedValue = ! shallowEqual ( encodedValueCacheRef . current , parsedQuery [ name ] ) ; const encodedValue = hasNewEncodedValue ? parsedQuery [ name ] : encodedValueCacheRef . current ; if ( ! hasNewEncodedValue && ! hasNewParamConfig && decodedValueCacheRef . current !== undefined ) { return decodedValueCacheRef . current ; } const newDecodedValue = paramConfig . decode ( encodedValue ) ; const hasNewDecodedValue = ( ( decodedValueCacheRef . current == null || newDecodedValue == null ) && decodedValueCacheRef . current === newDecodedValue ) || ! isValueEqual ( decodedValueCacheRef . current as D2 , newDecodedValue ) ; return hasNewDecodedValue ? newDecodedValue : ( decodedValueCacheRef . current as D2 ) ; } /**
 * Given a query param name and query parameter configuration ({ encode, decode })
 * return the decoded value and a setter for updating it.
 *
 * The setter takes two arguments (newValue, updateType) where updateType
 * is one of 'replace' | 'replaceIn' | 'push' | 'pushIn', defaulting to
 * 'pushIn'.
 *
 * You may optionally pass in a rawQuery object, otherwise the query is derived
 * from the location available in the context.
 *
 * D = decoded type
 * D2 = return value from decode (typically same as D)
 */ export const useQueryParam = < D , D2 = D > ( name , paramConfig = StringParam as QueryParamConfig < any > ) : [ D2 , ( newValue : NewValueType < D > , updateType ? : UrlUpdateType ) => void ] => { const { location , getLocation , setLocation } = useLocationContext ( ) ; const parsedQuery = sharedMemoizedQueryParser ( getSSRSafeSearchString ( location ) ) ; const encodedValueCacheRef = React . useRef ( ) ; const paramConfigRef = React . useRef ( paramConfig ) ; const decodedValueCacheRef = React . useRef < D2 | undefined > ( ) ; const decodedValue = getLatestDecodedValue ( location , name , paramConfig , paramConfigRef , encodedValueCacheRef , decodedValueCacheRef ) ; useUpdateRefIfShallowNew ( encodedValueCacheRef , parsedQuery [ name ] ) ; useUpdateRefIfShallowNew ( paramConfigRef , paramConfig ) ; useUpdateRefIfShallowNew ( decodedValueCacheRef , decodedValue , paramConfig . equals ) ; const setValueDeps = { paramConfig , name , setLocation , getLocation , } ; const setValueDepsRef = React . useRef ( setValueDeps ) ; setValueDepsRef . current = setValueDeps ; const setValue = React . useCallback ( function setValueCallback ( newValue , updateType ? ) : void { const deps = setValueDepsRef . current ; let newEncodedValue ; if ( typeof newValue === 'function' ) { const latestValue = getLatestDecodedValue ( deps . getLocation ( ) , deps . name , deps . paramConfig , paramConfigRef , encodedValueCacheRef , decodedValueCacheRef ) ; decodedValueCacheRef . current = latestValue ; newEncodedValue = deps . paramConfig . encode ( ( newValue as Function ) ( latestValue ) ) ; } else { newEncodedValue = deps . paramConfig . encode ( newValue ) ; } deps . setLocation ( { [ deps . name ] : newEncodedValue } , updateType ) ; } , [ ] ) ; return [ decodedValue , setValue ] ; } ;