0

I have created a custom route type for my project url. The function accepts two parameters,

  1. actual path url
  2. param for the url. eg) "/project/:projectId/user/:userId" => {projectId:"...",userId:"..."}

My second parameter (param) in the function can be undefined if there is no query path like project url in the code. But I find it difficult to add optional type when param type is undefiend. Anyone has better solution?

const projectDetailUrl = "/project/:id";
const projectUrl = "/project";

export type ExtractParams<T extends string> =
  T extends `${infer _Start}:${infer Param}/${infer _End}`
    ? Param | ExtractParams<_End>
    : T extends `${infer _Start}:${infer LastParam}`
    ? LastParam
    : never;

export type CreateParamObject<T extends string> = ExtractParams<T> extends
  | never
  | undefined
  ? undefined
  : {
      [K in ExtractParams<T>]: string;
    };


function push<T extends string>(url: T, params: CreateParamObject<T>) {
 console.log(url)
 console.log(params)
}


push(projectUrl,undefined)
push(projectDetailUrl,{id:"1"})

I want to removed undefiend without an error but keep params type if there is query path

AS IS 
push(projectUrl,undefined)
push(projectDetailUrl,{id:"1"})


TO BE
push(projectUrl)
push(projectDetailUrl,{id:"1"})
1
  • You can't make a function parameter "conditionally optional" directly, but you could switch to a rest parameter whose length can conditionally be zero or one, as shown in this playground link. Does that fully address the question? If so I'll write up an answer explaining (or find a suitable duplication source); if not, what am I missing? Commented Jun 10, 2024 at 12:05

1 Answer 1

0

You can do that with generics and a conditional type using rest parameters.

First infer the parameters of the URL with a generic parameter and your type ExtractParams. Since that type either returns a union of strings or never we can use a conditional type on it to check the result. If [ExtractParams<T>] extends [never] we know there are no params and return [{ [x: string]: string }?] to represent an optional object with string keys and string values. Otherwise we return the computed parameter object with [CreateParamObject<T>].

See Optional parameters based on conditional types and microsoft/TypeScript/pull/24897 for more information about tuples in rest parameters and spread expressions.

const projectDetailUrl = "/project/:id";
const projectUrl = "/project";

export type ExtractParams<T extends string> =
  T extends `${infer _Start}:${infer Param}/${infer _End}`
    ? Param | ExtractParams<_End>
    : T extends `${infer _Start}:${infer LastParam}`
    ? LastParam
    : never;

export type CreateParamObject<T extends string> = {
  [K in ExtractParams<T>]: string;
};

function push<T extends string, U extends ExtractParams<T>>(
  url: T,
  ...params: [U] extends [never]
    ? [{ [x: string]: string }?]
    : [CreateParamObject<T>]
) {
  console.log(url);
  console.log(params);
}

push(projectUrl); // okay
push(projectUrl, { whatever: "1" }); // okay
push(projectDetailUrl, { id: "1" }); // okay 
push(projectDetailUrl); // error 

TypeScript Playground

Sign up to request clarification or add additional context in comments.

3 Comments

It is possible to simply use [] instead of [{ [x: string]: string }?]. Then there won't be a second parameter at all. Meaning you won't be able to access it inside the function body. Or if you want it there you can use [undefined?] instead. Does that work for you? If so, I'll update my answer accordingly.
thats a good solution. what if i want to show the type when i hover the code in the editor for better developer experience, spread expression doesn't seem working and also if i change the type of the parameter to one argument like {url:"",params:{id:"s"}, options:{...}} in the near future, it wont work either.
Well now we're getting off track of the original question... One possible way is to use a conditional type to add an optional property params to the object type as shown in this playground. If you have any further issues, I suggest you either edit your question or better post a new one. It's hard for other people to figure out what's going on if you change your requirements.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.