Home > Back-end >  Nested recursive Type Hinting in TypeScript?
Nested recursive Type Hinting in TypeScript?

Time:10-18

I'm not entirely sure if I'm calling this right so I'll explain.

I'm creating React app using TypeScript, in order to make my life simpler I want to store all routings paths as constants separately, I also want to have TypeScript like auto-completion in VS Code. So I have this king of structure:

type GlobalRoutingT = 'user' | 'listing';

type RoutingVariants = {
  list: string,
  add: string,
  get: string,
  [key: string]: RoutingVariants | string,
};

type RouterT = Record<GlobalRoutingT, RoutingVariants>;

export const Routes: RouterT = {
  user: {
    list: '/user',
    add: '/user/add',
    get: '/user/:id',
    invoices: {
      list: '/user/invoices',
      add: '/user/invoices/add',
      get: '/user/invoices/:id',
    }
  },
  listing: {
    list: '/listing',
    add: '/listing/add',
    get: '/listing/:id',
  },
};

It works all great I can use it like this at the moment

<Route path={Routes.user.invoices.get} exact >
  <GetInvoice />
</Route>

But inside VS Code I get error:

Property 'get' does not exist on type 'string | RoutingVariants'. Property 'get' does not exist on type 'string'.

and Routes.user.invoices.get part is highlighted.

What am I doing wrong?

CodePudding user response:

It looks like what you want isn't exactly possible: How to combine known interface properties with a custom index signature?

You can't combine an index signature with named keys on the same interface / type.

As a workaround, you could use a "child" property, like this:

type GlobalRoutingT = 'user' | 'listing';

type RoutingVariants = {
    list: string,
    add: string,
    get: string,
    child?: Record<string, RoutingVariants>
};

type RouterT = Record<GlobalRoutingT, RoutingVariants>;

export const Routes: RouterT = {
    user: {
        list: '/user',
        add: '/user/add',
        get: '/user/:id',
        child: {
            invoices: {
                list: '/user/invoices',
                add: '/user/invoices/add',
                get: '/user/invoices/:id'
            }
        }
    },
    listing: {
        list: '/listing',
        add: '/listing/add',
        get: '/listing/:id'
    }
};

const a = Routes.user.child?.invoices.get;

Notice how I have to use ?. to access the child, as it's optional on the type definition.

You could replace child with something shorter, a single letter, $ or _...

CodePudding user response:

You need to force the type cast, try this:

<Route path={(Routes.user.invoices as RoutingVariants).get} exact >
  <GetInvoice />
</Route>
  • Related