typed-route-config
v2.0.5
Published
Utility to created route configuration that is typed
Downloads
29
Readme
Typed Route Config
Type safe configuration for routes.
Installation
npm install typed-route-config
The Problem
If you use react-router, you might want to centralise your routes in some kind of configuration object, which can then be looked up in all the places you use them.
For example, you may have:
import React from 'react'
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'
export const App: React.FC = () => (
<BrowserRouter>
<Switch>
<Route path={'/dashboard/me'} render={() => <div>the dashboard profile component here...</div>}/>
<Route path={'/dashboard/offer/:offerId/documents'} render={() => <div>the documents component here...</div>}/>
</Switch>
</BrowserRouter>
)
type LinkProps = {
offerId: string
}
export const LinkToDocuments: React.FC<LinkProps> = ({ offerId }) => (
<Link to={`/dashboard/offer/${offerId}/documents`} />
)
So, suppose you to factor these routes out into a configuration that looked like this:
// myRoutes.ts
export const mySimpleRouteConfig = {
'dashboard': '/dashboard',
'dashboard.me': '/dashboard/me',
'dashboard.offer': '/dashboard/offer/:offerId',
'dashboard.offer.collaborators': '/dashboard/offer/:offerId/collaborators',
'dashboard.offer.documents': '/dashboard/offer/:offerId/documents',
}
Which would then look something like this:
import React from 'react'
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'
import { mySimpleRouteConfig } from './myRoutes'
export const App: React.FC = () => (
<BrowserRouter>
<Switch>
<Route path={mySimpleRouteConfig['dashboard.me']} render={() => <div>the dashboard profile component here...</div>}/>
<Route path={mySimpleRouteConfig['dashboard.offer.documents']} render={() => <div>the documents component here...</div>}/>
</Switch>
</BrowserRouter>
)
type LinkProps = {
offerId: string
}
export const LinkToDocuments: React.FC<LinkProps> = ({ offerId }) => {
const documentsPath = mySimpleRouteConfig['dashboard.offer.documents'] // '/dashboard/offer/:offerId/documents'
// interpolate the offerId into documentsPath...
const documentsUrlForThisOfferId = interpolate(documentsPath, { offerId })
return (
<Link to={documentsUrlForThisOfferId} />
)
}
The issue here is you have no typesafety with how you've implented the interpolate
function!
The Solution
Using typed-route-config
could re-write your config as:
// myRoutes.ts
import { createRoutes } from 'typed-route-config';
export const { route, path } = createRoutes(root => ({
'dashboard': root.path('dashboard'),
'dashboard.me': root.path('dashboard/me'),
'dashboard.offer': root.path('dashboard/offer').param('offerId'),
'dashboard.offer.collaborators': root.path('dashboard/offer').param('offerId').path('collaborators'),
'dashboard.offer.documents': root.path('dashboard/offer').param('offerId').path('documents')
}))
And then continuing with our example, we would then get
import React from 'react'
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'
import { route, path } from './myRoutes'
export const App: React.FC = () => {
// const fakePath = path('fake.route')
// Argument of type '"fake.route"' is not assignable to parameter of type '"dashboard" | "dashboard.me" | ... | "dashboard.offer.documents"
// path() will only let you use keys from the config! It returns the un-interpolated version of that route
// e.g. path('dashboard.offer.documents') -> 'dashboard/offer/:offerId/documents'
return (
<BrowserRouter>
<Switch>
<Route path={path('dashboard.me')} render={() => <div>the dashboard profile component here...</div>}/>
<Route path={path('dashboard.offer.documents')} render={() => <div>the documents component here...</div>}/>
</Switch>
</BrowserRouter>
)
}
type LinkProps = {
offerId: string
}
export const LinkToDocuments: React.FC<LinkProps> = ({ offerId }) => {
// route() interpolates the offerId into path for that route, but most importantly in a typesafe way
const documentsUrlForThisOfferId = route('dashboard.offer.documents', { offerId })
// const error = route('dashboard.offer.documents', { monkeyId: 'abc' })
// Argument of type '{ monkeyId: string; }' is not assignable to parameter of type '{ offerId: string; }'
return (
<Link to={documentsUrlForThisOfferId} />
)
}
We can go a step further and neaten up our route config to remove the repetition.
You can use the .group(...)
to nest routes. The config from before could be re-written as:
import { createRoutes } from 'typed-route-config';
export const { routes, route, path } = createRoutes(root => ({
'dashboard': root.path('dashboard').group(dashboard => ({
// '' within a group gives us a "root" route with the name of the group i.e. 'dashboard'
'': dashboard,
// joins the group name to this route name i.e. 'dashboard.me'
'me': dashboard.path('me'),
// and you can nest groups
'offer': dashboard.path('offer').param('offerId').group(offer => ({
'': offer, // 'dashboard.offer'
'collaborators': offer.path('collaborators'), // 'dashboard.offer.collaborators'
'documents': offer.path('documents') // 'dashboard.offer.documents'
}))
})),
}))
Usage
Define your routes somewhere in your application using the createRoutes
function:
// routes.ts
import { createRoutes, MakeRouteParams } from 'typed-route-config'
const { routes, route, path } = createRoutes(root => ({
// ... routes go here
}))
// only export the functions you need, there are more explained later not shown in this basic example
// you probably don't want to export 'routes' as it is just used to generate the helper types below
export { route, path }
type Routes = typeof routes
// union type of every routeName
export type RouteNames = keyof Routes
// gets the route params object for the given routeName
export type RouteParams<N extends RouteNames> = MakeRouteParams<Routes, N>
TODO explain the rest of the API
Building from source
To build, just run npm run build
To make a change
Make your change, (don't forget to npm run build
), then increment the version number in the package.json before pushing.
In the projects which depend on this package, run npm update typed-route-config
.
npm detects changes using the version number in the package.json