Useful types: Extract route params with TypeScript

bytimo

Andrei Kondratev

Posted on May 2, 2022

Useful types: Extract route params with TypeScript

Recently, I started working on a new project with React and TypeScript. I used react-router-dom@6 for routing and a few of my routes look like this

/card
/card/:cardId
/card/:cardId/transaction
/card/:cardId/transaction/:transactionId
// ... and so on
Enter fullscreen mode Exit fullscreen mode

So, I really love TypeScript and always try to create strong typed system especially if types will be inferred automatically. I tried to define a type that would infer a route's parameters type using the path. This is a story about how to do it.

As you know TypeScript has conditional types and template literal types. TypeScript also allows to use generics with all of these types. Using conditional types and generics you can write something like this

type ExctractParams<Path> = Path extends "card/:cardId" 
   ? { cardId: string }
   : {}

type Foo1 = ExctractParams<"card"> // {}
type Foo2 = ExctractParams<"card/:cardId"> // {cardId: string}
Enter fullscreen mode Exit fullscreen mode

Inside a conditional type we can use a template literal type to discover a parameter in the path. Then we can use an infer keyword to store an inferred type to a new type parameter and use the parameter as a result of the conditional type. Look at this

type ExctractParams<Path> = Path extends `:${infer Param}`
   ? Record<Param, string>
   : {}

type Bar1 = ExctractParams<"card"> // {}
type Bar2 = ExctractParams<":cardId"> // {cardId: string}
type Bar3 = ExctractParams<":transactionId"> // {transactionId: string}
Enter fullscreen mode Exit fullscreen mode

It's OK, but what about more complex path? We can also use the infer and template types to split the path into segments. The main idea of the extracting type is to split off one segment, try to extract a parameter from this segment and reuse the type with the rest of the path recursively.
It may be implemented like this

type ExctractParams<Path> = Path extends `${infer Segment}/${infer Rest}`
  ? Segment extends `:${infer Param}` ? Record<Param, string> & ExctractParams<Rest> : ExctractParams<Rest>
  : Path extends `:${infer Param}` ? Record<Param, string> : {}

type Baz1 = ExctractParams<"card"> // {}
type Baz2 = ExctractParams<"card/:cardId"> // {cardId: string}
type Baz3 = ExctractParams<"card/:cardId/transaction"> // {cardId: string}
type Baz4 = ExctractParams<"card/:cardId/transaction/:transactionId"> // {cardId: string, transactionId: string}
Enter fullscreen mode Exit fullscreen mode

But in this case, if the path can't be splitted by segments we have to try to extract the parameter from Path because it may be the last segment of a complex path that can contain some parameter. So, we have a duplicated path extracting logic. I suggest separating this logic into another type. My finally solution is

type ExtractParam<Path, NextPart> = Path extends `:${infer Param}` ? Record<Param, string> & NextPart : NextPart;

type ExctractParams<Path> = Path extends `${infer Segment}/${infer Rest}`
  ? ExtractParam<Segment, ExctractParams<Rest>>
  : ExtractParam<Path, {}>

const foo: ExctractParams<"card"> = {};
const bar: ExctractParams<"card/:cardId"> = {cardId: "some id"};
const baz: ExctractParams<"card/:cardId/transaction/:transactionId"> = {cardId: "some id", transactionId: "another id"}


//@ts-expect-error
const wrongBar: ExctractParams<"card/:cardId"> = {};
//@ts-expect-error
const wrongBaz: ExctractParams<"card/:cardId/transaction/:transactionId"> = {cardId: "some id"};

Enter fullscreen mode Exit fullscreen mode

I use a special type parameter NextPart to determine what it has to do after extracting - try to extract parameters from the rest of the path or stop recursion.

I hope this story's been useful for you, you've learned something new. Maybe now you can improve something in your project.

Later I'm going to write a story about how I've implemented a route tree with the extracting parameters type and how I've used that with React and react-router-dom. Thanks

💖 💪 🙅 🚩
bytimo
Andrei Kondratev

Posted on May 2, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related