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

react-typesafe-routes

v2.0.0-alpha.0

Published

The last routing library you will ever need in your react projects - Completely typesafe and easy to use.

Downloads

477

Readme

React (Awesome) Typesafe Routes

CI Version Downloads

The last routing library you will ever need in your React projects. (At least if you're using react-router–dom but also why wouldn't you?)

Typedoc documentation

Table of Contents

Installing

Make sure you are using at least Typescript 4.1.2 in your project. To find out what version you are using use npm ls typescript. There is a known issue with react-scripts 4.0.1 still requiring Typescript 3 but you can circumvent that by adding a --legacy-peer-deps to your install command.

npm install react-typesafe-routes

or

yarn add react-typesafe-routes

Example

Router definition

const defaultOptions = {
  appBar: true,
};

const AuthMiddleware: RouteMiddleware = next => {
  if (isAuthenticated) {
    return next;
  } else {
    return () => <Redirect to={router.login()} />;
  }
};

export const router = OptionsRouter(defaultOptions, route => ({
  home: route('/', {
    component: HomePage,
  }),
  login: route('/login', {
    component: LoginPage,
    options: { appBar: false },
  }),
  players: route(
    '/players',
    {
      component: PlayersPage,
      middleware: AuthMiddleware,
    },
    route => ({
      info: route(
        '/:name/:id',
        {
          component: PlayerInfoPage,
          params: {
            name: stringParser,
            id: intParser,
          },
        },
        route => ({
          rating: route('/rating/:id', {
            component: PlayerRatingPage,
            params: { id: intParser },
          }),
          ban: route('/rating/:id', {
            component: PlayerRatingPage,
            params: { id: intParser },
          }),
        })
      ),
    })
  ),
}));

Usage in App

The BrowserRouter comes from react-router-dom you can use any Router from that package that you like.

const AppBar = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to={router.home()}>Home</Link>
        </li>
        <li>
          <Link to={router.player()}>Players</Link>
        </li>
      </ul>
    </div>
  );
};

const App = () => {
  const { appBar } = useRouteOptions(router);

  return (
    <BrowserRouter>
      <div>
        {appBar && <AppBar />}
        <RouterSwitch router={router} />
      </div>
    </BrowserRouter>
  );
};

Route programmatically

To go to a route programmatically / without a Link Component:

const navigate = useNavigate();
navigate(router.players().player({ id: 1, name: 'playerName' }).$);

The function will require you to input required parameters and don't forget the dollar sign at the end.

Types of Routers

OptionsRouter

This is the router most people will probably use. It supports Global options that configurable on a per Route basis and they automatically apply for child routes.

For example the login route is supposed to be full screen and doesn't require the AppBar.

const defaultOptions = {
  appBar: true,
};

const router = OptionsRouter(defaultOptions, route => ({
  home: route('/', {
    component: HomePage,
  }),
  login: route('/login', {
    component: LoginPage,
    options: { appBar: false }
  }),
});

const App = () => {
  const options = useRouteOptions(router);

  return (
    <div>
      {options.appBar && <AppBar>}
      <RouterSwitch router={router} />
    </div>
  );
};

Router

The Router is basically the same as the OptionsRouter but it doesn't have Options as the name already implied. No idea why you would need this but it's there just in case.

const router = Router(route => ({
  home: route('/', {
    component: HomePage,
  }),
  login: route('/login', {
    component: LoginPage,
  }),
});

const App = () => {
  return (
    <div>
      <RouterSwitch router={router} />
    </div>
  );
};

Routes

Routes can only be create inside an OptionsRouter or a Router.

const options = { appBar: true };
const router = OptionsRouter(options, route => ({
  home: route(
    /**
     * The route template
    */
    '',
    {
      // The Component to be rendered on this route.
      component: RouteComponent;

      // The Parsers for the parameters in this route.
      params: Record<string, ParamParser<any>>;

      // A middleware for this route
      middleware?: RouteMiddleware;

      // Global options for this route
      options?: Partial<RO>;

      // Wether or not to include this routes child routes in a RouterSwitch  - Defaults to true
      includeChildren?: boolean;
    }
  ),
});

Route Template and Parameters

Every route requires a component to be defined and for every parameter you define you are required to define a parser.

Basic parameters

Basic parameters are defined with a colon in front of them.

const router = Route(route => ({
  test: route('test/:id', {
    component: TestPage,
    params: {
      id: intParser,
    },
  }),
}));

Optional parameters

If you want a parameter to be optional you can add a question mark behind it. Optional parameters still require a parser to be defined.

const router = Route(route => ({
  test: route('test/:id?', {
    component: TestPage,
    params: {
      id: intParser,
    },
  }),
}));

Query parameters

A query parameter has an ampersand in front of it, they can be chained and also be made optional with a question mark.

const router = Route(route => ({
  test: route('test/:id?&:filter&:page?', {
    component: TestPage,
    params: {
      id: intParser,
      page: intParser,
      filter: stringParser,
    },
  }),
}));

Child Routes

Child routes can be defined with the third argument of the route function - Another route function!

const router = Route(route => ({
  test: route(
    'test/:id?&:filter&:page?',
    {
      component: TestPage,
      params: {
        id: intParser,
        page: intParser,
        filter: stringParser,
      },
    },
    route => ({
      child: route('test'),
    })
  ),
}));

Parameter Parsers

Every parameter has a parser which makes useRouteParams possible.

Available Parsers

The following are self explanatory:

  • stringParser
  • floatParser
  • intParser
  • dateParser
  • booleanParser

But there is also the stringListParser used like this:

// Probably defined in your Page file
const testTabs = ['overview', 'statistics', 'comments'] as const;

const router = Route(route => ({
  test: route('test&:tab', {
    component: TestPage,
    params: {
      tab: stringListParser(testTabs),
    },
  }),
}));

Which will result in your parameter being one of the tabs.

Your own parser

The general interface for a ParamParser is:

export interface ParamParser<T> {
  parse: (s: string) => T;
  serialize: (x: T) => string;
}

You can implement your own kind of parser as an example the intParser:

export const intParser: ParamParser<number> = {
  parse: s => parseInt(s),
  serialize: x => x.toString(),
};

Hooks

There are a few complementary Hooks to make your life easier.

useRouteOptions

This is useful whenever you need those global route options of an OptionsRouter. Since you define defaults in the Router those values will never be undefined and always return the correct values for your current route.

const options = { appBar: true };
const router = OptionsRouter(options, route => ({
  home: route('', {
    component: HomePage
  }),
  entry: route('entries/:id', {
    component: EntryPage
    params: {
      id: intParser
    }
  })
}));

const options = useRouteOptions(router);

// or destructured
const { appBar } = useRouteOptions(router);

useRouteParams

This is the way to go when you need those parameters of your Route. Let's say you have the Router from right above.

export const EntryPage = () => {
  // id is statically typed to be a number
  const { id } = useRouteParams(router.entry);

  return <div>Entry {id}</div>;
};

useRouteActive and useRoutesActive

This is the way to go when you need those parameters of your Route. Let's say you have the Router from right above.

const HighlightLink = (
  props: React.PropsWithChildren<{
    to: { $: string };
    isActive: boolean;
  }>
) => {
  const style: React.CSSProperties = { color: 'blue' };
  const activeStyle: React.CSSProperties = { color: 'red' };

  return (
    <Link to={props.to} style={props.isActive ? activeStyle : style}>
      {props.children}
    </Link>
  );
};

export const App = () => {
  // Check if a single route is active
  const active = useRouteActive(router.home);

  // Check if multiple routes are active
  const { home, entry } = useRoutesActive({
    home: router.home,
    entry: router.entry,
  });

  return (
    <ul>
      <li>
        <HighlightLink isActive={home} to={router.home()}>
          Home
        </HighlightLink>
      </li>
      <li>
        <HighlightLink isActive={entry} to={router.entry()}>
          Entry
        </HighlightLink>
      </li>
    </ul>
  );
};

Components

RouterSwitch

This is what you would use instead of the Switch and Route from react-router-dom. You just give it your router and it automatically adds al the routes for you.

<RouterSwitch router={router} />

Link

This is a simple wrapper Component for the react-router-dom Link.

<Link to={router.home()}></Link>

NavLink

This is a simple wrapper Component for the react-router-dom NavLink.

<NavLink to={router.home()}></NavLink>

Redirect

This is a simple wrapper Component for the react-router-dom Redirect.

<Redirect to={router.home()}></Redirect>

Route

This is a simple wrapper Component for the react-router-dom Route.

<Route to={router.home()}></Route>

Roadmap

  • Optional defaults for optional parameters
  • Parsing parent params in a nicer way

Contributing

All contributions are welcome. Please open an issue about your request or bug fix before submitting a pull request.

License

This project is licensed under the terms of the MIT license.