Let's dive into less frequent Typescript feature - function overloading with a realistic example.
Intro
Have a custom hook
export function useUrlState<T extends JSONCompatible>(
defaultState: T,
searchParams?: object,
)
At some moment I need to add more arguments to it, possibly more in the future. Hard to remember what Nth argument is, and calling a function like useUrlState(firstArg, null, null, fourthArg)
is ridiculous. It will be way easier to pass arguments inside an object like this:
export function useUrlState<T extends JSONCompatible>({
defaultState,
searchParams,
replace
}: { defaultState: T, searchParams?: object, replace?: boolean })
I will convert the function to a new format and keep it backward compatible with the existing implementation.
Implementation
First, need to add overload signatures right above function implementation. Overload signatures are all possible ways a function can be called, with different argument's type and quantity.
/**
* @deprecated Pass arguments in a object `useUrlState({ defaultState: form, searchParams })`
*
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#api}
*/
export function useUrlState<T extends JSONCompatible>(defaultState: T, searchParams?: object): {
state: DeepReadonly<T>,
updateState: (value: Partial<DeepReadonly<T>>,
updateUrl: (value?: Partial<DeepReadonly<T>>) => void,
getState: () => DeepReadonly<T>
}
/**
* NextJS hook. Returns `state`, `updateState`, and `updateUrl` functions
*
* @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
* @param {?SearchParams<T>} [searchParams] searchParams from Next server component
*/
export function useUrlState<T extends JSONCompatible>({ defaultState, searchParams }: {
defaultState: T, searchParams?: object, replace?: boolean
}): {
state: DeepReadonly<T>,
updateState: (value: Partial<DeepReadonly<T>>) => void,
updateUrl: (value?: Partial<DeepReadonly<T>>) => void,
getState: () => DeepReadonly<T>
} // <- notice that should implicitly define returned value
// implementation
export function useUrlState<T extends JSONCompatible>(
defaultState: T | { defaultState: T, searchParams?: object, replace?: boolean },
searchParams?: object,
) {
Tricky part is that signatures should be compatible with implementation, so have this defaultState: T | { defaultState: T, searchParams?: object, replace?: boolean }
I assume that if the first argument has a specific key, it is a new object format.
const _defaultState = ('defaultState' in defaultState ? defaultState.defaultState : defaultState) as T
const _searchParams = ('defaultState' in defaultState ? defaultState.searchParams : searchParams) as object | undefined
const _replace = ('defaultState' in defaultState ? defaultState.replace ?? true : false) as boolean
Also, can notice that replace
argument has default value true
for a new format, but for old one it's false
.
Let's see how it works.
Notice that we have different JSDoc comments for each signature, old one marked with @deprecated
tag.
Official docs https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads
Tnx for reading :)
Leave a comment about your experience, or if you have ideas how to do it more elegantly.
Top comments (0)