I'm trying to do the following:
shared.ts
export type ReverseMap<T> = T[keyof T];
/*
This is where i want to return a slightly modified type with the '/root/' appended to the values.
*/
export const getRoutesForPages = <T>( pages: T, root: string ): T => {
Object.keys(pages).forEach((key) => {
pages[key] = `/${root}/${pages[key]}`
});
return pages;
}
admin.ts
import { getRoutesForPages, ReverseMap } from './shared';
// root is basically the name of the lazy loaded module.
const ROOT = 'admin';
// pages are referenced by the routes within lazy loaded module
export const PAGES = {
PAGE_1: `p-1`,
PAGE_2: `p-2`,
} as const;
export type Page = ReverseMap<typeof PAGES>;
// gives me a type with 'p-1' and 'p-2'
/* creates a mapped routes object referenced throughout the application
export const ROUTES = {
PAGE_1: `/admin/p-1`,
PAGE_2: `/admin/p-2`,
} as const;
The issue is that it still returns interface of PAGES not the modified.
*/
const ROUTES = getRoutesForPages<typeof PAGES>(PAGES, ROOT);
// should create a type like '/admin/p-1' and 'admin/p-2'
export type Route = ReverseMap<typeof ROUTES>; // create a type from my route object
The type Route
returns 'p-1' and 'p-2'.
I would like it to return '/admin/p-1' and '/admin/p-2' as the type.
Ideally done within the getRoutesForPages
function for re-usability. My experience with generics and types is limited, so some help would be appreciated. Or if there is a better solution, please let me know.
CodePudding user response:
Add another generic parameter to your function to infer the type of the root
parameter. Then in the return type you can use a mapped type and reference the root
parameter with the generic parameter Root
. With template literal types, it's practically doing the same thing as your code.
const getRoutesForPages = <T extends Record<any, string>, Root extends string>( pages: T, root: Root ): { [K in keyof T]: `/${Root}/${T[K]}` } => {
pages = { ...pages };
(Object.keys(pages) as (keyof T)[]).forEach((key) => {
pages[key] = `/${root}/${pages[key]}` as T[keyof T]; // technically incorrect cast and only exists to suppress errors
});
return pages as ReturnType<typeof getRoutesForPages<T, Root>>;
};
Also, your original code mutates the pages
parameter, meaning your PAGES
constant actually got changed, which is a big no-no. That's why I have added pages = { ...pages };
to create a shallow copy we can modify.
You'll also notice the new type assertions in the body. We're doing icky things here so I think the easiest way is to just cast them to the right types.
const ROUTES = getRoutesForPages(PAGES, ROOT);
export type Route = ReverseMap<typeof ROUTES>;
You also don't have to explicitly pass any generics to your function as TypeScript can infer these for you. And your Route
type is now "/admin/p-1" | "/admin/p-2"
as desired.