DEV Community

Alex
Alex

Posted on

1

TS function overloading - real world example

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,
)
Enter fullscreen mode Exit fullscreen mode

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 })
Enter fullscreen mode Exit fullscreen mode

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,
) {
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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.

Result

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.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

⭐️🎀 JavaScript Visualized: Promises & Async/Await

async await

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay