npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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