Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional parameter for curried function based on generic

Tags:

typescript

How to make the params conditional only if no generic supplied?

type Params<T> = T extends Record<string, string> ? T : never;

export const getPath =
  <T>(path: string) =>
  (params: Params<T>) => {
    const lang = getCurrentLang();

    return generatePath(path, { lang, ...(params ?? {}) });
  };

For example:

const login = getPath("/:lang/login")
login() // should works without types error

const book = getPath<{ id: string }>("/:lang/books/:id")
book() // error
book({ id: "1" }) // works
like image 300
Mahmoud Ashraf Avatar asked Aug 07 '26 15:08

Mahmoud Ashraf


1 Answers

You can use generics in combination with a conditional type to enforce a certain return type for getPath(). Let your generic parameter T extend a Union Type of your conditional return value and something that is not assinable to that value (e. g. undefined) so you can check if a generic argument was passed or not.

If a generic argument was passed, return (param: T) => ... otherwise just () => .... This logic can be simplified even more using tuples in rest parameters and spread expressions:
(...params: T extends Record<string, string> ? [T] : [] ) => any

export const getPath = <
  T extends Record<string, string> | undefined = undefined
>(
  path: string
): ((
  ...params: T extends Record<string, string> ? [T] : []
) => any) => {
  return null as any; // ignoring the implemetation
};

const login = getPath("/:lang/login");
login(); // works

const book = getPath<{id: string}>("/:lang/books/:id");
//    ^? const book: (param: {id: string;}) => any
book(); // error
book({id: "1"}); // works

TypeScript Playground


P. S. Note, I removed the function implementation as I don't know how getCurrentLang() and generatePath() are defined or what their return type is.

like image 77
Behemoth Avatar answered Aug 11 '26 13:08

Behemoth