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

typesafe-router

v0.1.0

Published

A tiny wrapper library for React Router that dramatically improves type safety.

Downloads

18

Readme

Typesafe Router

Intro

A tiny wrapper library for React Router that dramatically improves type safety.

Features

  • Static types and autocompletion
  • Builder API
  • Zero dependencies and tiny footprint
  • No code generation
  • Supports lazy loading

Caveats

  • Pre 1.0 (expect additional features and API changes)
  • React Fast Refresh is currently not supported

Quickstart

Jump right in with a quickstart example:

Open in StackBlitz

Installation

Requires react-router-dom version 6.9 or above

npm install typesafe-router

Tutorial

Typesafe Router splits your route definitions into 3 sections:

  • Path matching fields such as path, index and children
  • Data loading/submitting fields such as loader and action
  • Rendering fields such as Component and ErrorBoundary

This approach unlocks great benefits, both in terms of type safety and lazy-loading.

These definitions borrow from Matt Brophy's excellent blog post on the new React Router lazy-loading features (https://remix.run/blog/lazy-loading-routes)

Create the route config, using just the path matching fields, and then export the type

// routes.ts
import { createRouteConfig } from 'typesafe-router';

const routeConfig = createRouteConfig([
  {
    path: '/',
    children: [
      {
        path: 'child',
      },
    ],
  },
  {
    path: 'another-route',
  },
] as const);

export type RouteConfig = typeof routeConfig;

NOTE: Make sure to include as const after the array. This requirement will be removed once TypeScript 5 is more widely adopted.

Initialise the data creator functions and add the React Router utils that will be used in your actions and loaders

// utils.ts
import { initDataCreators } from 'typesafe-router';
import { redirect } from 'react-router-dom';
import type { RouteConfig } from './routes';

export const { createAction, createLoader } =
  initDataCreators<RouteConfig>().addUtils({ redirect });

Use the exported createAction and createLoader functions to create your actions and loaders

// exampleRoute.tsx
import { createAction, createLoader } from './utils';

export const exampleAction = createAction('/', ({ params, redirect }) => {
  return 'a string';
});

export const exampleLoader = createLoader('/', ({ params, redirect }) => {
  return 123;
});

NOTE: The first argument is the route ID. Route IDs are automatically generated from your paths, with '_' segments added for pathless routes and '_index' segments added for index routes. You can also provide your own IDs in the route config if you prefer.

Create the data config by adding your actions and loaders to the route config and then export the type

// routes.ts
/* existing imports */
import { exampleAction, exampleLoader } from './exampleRoute';

/* existing code */

const dataConfig = routeConfig
  .addActions(exampleAction)
  .addLoaders(exampleLoader);

export type DataConfig = typeof dataConfig;

Initialise the render creator functions and add the React Router utils that will be used in your components and error boundaries

// utils.ts
/* existing imports */
import { initRenderCreators } from 'typesafe-router';
import {
  Link,
  useActionData,
  useLoaderData,
  useParams,
} from 'react-router-dom';
import type { DataConfig } from './routes';

/* existing code */

export const { createComponent, createErrorBoundary } =
  initRenderCreators<DataConfig>().addUtils({
    Link,
    useActionData,
    useLoaderData,
    useParams,
  });

Use the exported createComponent and createErrorBoundary functions to create your components

// exampleRoute.tsx
/* existing imports */
import { createComponent, createErrorBoundary } from './utils';

export const exampleComponent = createComponent(
  '/',
  ({ useLoaderData, Link }) =>
    () => {
      const loaderData = useLoaderData();

      return (
        <>
          <p>The data is: {loaderData}</p>
          <Link to="/another-route">Link</Link>
        </>
      );
    }
);

export const exampleErrorBoundary = createErrorBoundary('/', () => () => {
  return <p>Error text</p>;
});

Create the routes by adding the components and error boundaries to the data config and calling toRoutes()

// routes.ts
/* existing imports */
import { exampleComponent, exampleErrorBoundary } from './exampleRoute';

/* existing code */

export const routes = dataConfig
  .addComponents(exampleComponent)
  .addErrorBoundaries(exampleErrorBoundary)
  .toRoutes();

Use the exported routes to create the router

// main.tsx
import { routes } from './routes';
import { createBrowserRouter } from 'react-router-dom';

const router = createBrowserRouter(routes);

BONUS: adding a lazy-loaded route component

Create a new file and component

// another-route.tsx
import { createComponent } from './utils';

export const Component = createComponent('/another-route', () => () => {
  return <h2>Lazy-loaded route component</h2>;
});

Import the lazy function and use it to add a dynamic import for the new route component

// routes.ts
/* existing imports */
import { lazy } from 'typesafe-router';

/* existing code */

export const routes = dataConfig
  .addComponents(
    exampleComponent,
    lazy('/another-route', () => import('./another-route'))
  )
  .addErrorBoundaries(exampleErrorBoundary)
  .toRoutes();

TIP: lazy can also be used to import loaders, actions and error boundaries. If you are combining lazy and static imports (e.g. a static loader and a lazy component) make sure they are in different files.

API

React Router imports

Typesafe Router currently supports the following React Router utils.

  • redirect
  • Form (initial support)
  • Link
  • NavLink
  • Navigate
  • useActionData
  • useLoaderData
  • useNavigate
  • useParams
  • useRouteLoaderData
  • useSubmit (initial support)
  • more coming soon...

This guide will highlight any differences from their React Router equivalents.

NOTE: Paths are defined as string literals and the possible relative and absolute values are inferred. If the path includes dynamic segments then these are required and defined in a params object. A searchParams object and/or hash string can also be defined. For components (<Link> etc.) these objects are provided as props. For hooks (useNavigate etc.) they are provided as properties of the second argument.

redirect

The path is inferred (see note above).

Link

The path is inferred (see note above)

Navigate

The path is inferred (see note above)

NavLink

The path is inferred (see note above)

useActionData

The return type is inferred from the action function defined on the route.

useLoaderData

The return type is inferred from the loader function defined on the route.

useNavigate

The path is inferred (see note above).

useParams

The params are inferred. The type is the union of all the possible combinations for the current route. Params set higher in the route hierarchy are always available whereas params set lower can be accessed by narrowing the type.

useRouteLoaderData

The return type is inferred from the loader function with the given ID. IDs are restricted to routes that have loaders and could be rendered at the same time as the current route. If the loader is lower in the route hierarchy then the return type is a union with undefined.