@mamawosh/router-config
TypeScript icon, indicating that this package has built-in type declarations

0.1.0-beta.4 • Public • Published

mamawosh-router-config

Mama Wosh Router Config

A set of convenience features on top of react-router-config, a component tree hierarchy generator for react-router.

System Requirements

Install the latest versions of the following software (some have been given minimum viable versions):

Installing version-bump-prompt

Have NPM install it globally.

npm install --global version-bump-prompt

Usage

Prerequisites

Before installing this package, make sure that the latest versions of these packages have already added to your dependencies:

  • react
  • react-router
  • react-router-config
  • All @types/* definitions for the packages mentioned if your project uses typescript.

Commands

Install this package:

NPM

npm install --save @mamawosh/router-config

Yarn

yarn add @mamawosh/router-config

Example

Let's assume that your project directory looks like this:

awesome-project
├── node_modules
├── public
├── src
│   ├── components
│   │   ├── Hello.jsx
│   │   ├── User.jsx
│   │   └── World.jsx
│   ├── pages
│   │   └── NotFound.jsx
│   ├── root.reducers.js
│   ├── root.routes.js
│   ├── Root.jsx
│   └── index.js
... other files

Create a file containing all of your routes. Call this your "root route" file.

awesome-project/src/root.routes.js

import { RouteConfigComponent, patchRouteConfigPaths } from '@mamawosh/router-config';

import NotFound from './pages/NotFound';

import Hello from './components/Hello';
import World from './components/World';
import User from './components/User';


const rootRoutes = patchRouteConfigPaths([ // Prefixes your route trees so that you don't have to!
  {
    path: '/',
    component: RouteConfigComponent, // Renders the subroutes without adding any extra content.
    routes: [
      {
        path: '/hello',
        component: Hello,
        routes: [
          {
            path: '/'
            exact: true,
            component: RouteConfigComponent,
          },
          {
            path: '/world',
            component: World,
          },
          {
            path: '/:user',
            component: User,
          },
        ],
      },
    ]
  },
  {
    path: '*',
    component: NotFound,
  },
]);

// Logging rootRoutes results in:
// [
//   {
//     path: '/',
//     component: f RouteConfigComponent(),
//     routes: [
//       {
//         path: '/hello',
//         component: Hello,
//         routes: [
//           {
//             path: '/hello'
//             exact: true,
//             component: f RouteConfigComponent(),
//           },
//           {
//             path: '/hello/world',
//             component: f World(),
//           },
//           {
//             path: '/hello/:user',
//             component: f User(),
//           },
//         ],
//       },
//     ]
//   },
//   {
//     path: '*',
//     component: f NotFound(),
//   },
// ]


export default rootRoutes;

Render the entire route tree at your root component:

awesome-project/src/Root.jsx

import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';

import rootRoutes from './root.routes';


function Root() {
  return (
    <BrowserRouter>
      {renderRoutes(rootRoutes)}
    </BrowserRouter>
  );
}


export default Root;

Apply the same renderRoutes to each file expecting subroutes:

awesome-project/src/Hello.jsx

import React from 'react';
import { renderRoutes } from 'react-router-config';


function Hello({ route, name }) {
  // NOTE: "route" isn't always defined. See TypeScript example below.
  return (
    <div>
      Hello, {name}!
      {route && renderRoutes(route.routes)}
    </div>
  );
}


export default Hello;

TypeScript equivalent: awesome-project/src/Hello.tsx

import React from 'react';
import { RouteConfigComponentProps, renderRoutes } from 'react-router-config';


interface HelloProps {
  name: string;
}

// Explicitly expect "route" props to be passed to this component.
// Wrap the existing "HelloProps" with "RouteConfigComponentProps" to apply the predicted values.
export const Hello = function Hello({ route, name }: RouteConfigComponentProps<HelloProps>) {
  const childRoutes = route
    ? renderRoutes(route.routes)
    : undefined;

  return (
    <div>
      Hello, {name}!
      {childRoutes}
    </div>
  );
}

API

RouteConfig

This is actually a type definition from @types/react-router-config and is not exported by this library if your project uses TypeScript (you should rely on the aforementioned typings for that), but is included here for reference purposes since it is also used very frequently by this library. JavaScript-only developers are also welcome to use to these definitions as guidance.

export interface RouteConfig {
  key?: React.Key;
  location?: Location;
  component?: React.ComponentType<RouteConfigComponentProps<any>> | React.ComponentType;
  path?: string;
  exact?: boolean;
  strict?: boolean;
  routes?: RouteConfig[];
  render?: (props: RouteConfigComponentProps<any>) => React.ReactNode;
  [propName: string]: any;
}

For further reading, see the react-router-config documentation.

patchTreesRoutePath(trees?: RouteConfig[]): RouteConfig[] | undefined

Prefixes the paths of an array of RouteConfig objects and their nested RouteConfig objects with the paths of their parents. If no RouteConfig array has been provided, the function will return undefined.

The patching is recursive; once you call this function on the root of the tree, all of its leaves will also be patched.

That means if your configuration looks like...

import { patchRouteConfigPaths } from '@mamawosh/router-config';

const oldRoutes = [
  {
    path: '/level-1',
    component: LevelOne,
    routes: [
      {
        path: '/level-2',
        component: LevelTwo,
        routes: [
          {
            path: '/level-3'
            component: LevelThree,
          },
        ],
      },
    ],
  },
];
const newRoutes = patchRouteConfigPaths(oldRoutes);

... then this function generates a result the equivalent of:

const newRoutes = [
  {
    path: '/level-1',
    component: LevelOne,
    routes: [
      {
        path: '/level-1/level-2',
        component: LevelTwo,
        routes: [
          {
            path: '/level-1/level-2/level-3'
            component: LevelThree,
          },
        ],
      },
    ],
  },
];

This function was built on the expectation that you will use react-router-config's renderRoutes(routes?: RouteConfig[]) function verbatim. If, instead, you prefer to prefix paths starting at specific node levels, use the next function, patchTreeRoutePath(tree: RouteConfig): RouteConfig.

patchTreeRoutePath(tree: RouteConfig): RouteConfig

Prefixes the path of a RouteConfig object. Unlike the previous function, the tree parameter is required.

The patching is recursive; once you call this function on the root of the tree, all of its leaves will also be patched.

import { patchRouteConfigPath } from '@mamawosh/router-config';

const oldRoute = {
  path: '/level-1',
  component: LevelOne,
  routes: [
    {
      path: '/level-2',
      component: LevelTwo,
    },
  ],
};
const newRoute = patchRouteConfigPaths(oldRoute);

... is the equivalent of:

const newRoute = {
  path: '/level-1',
  component: LevelOne,
  routes: [
    {
      path: '/level-1/level-2',
      component: LevelTwo,
    },
  ],
};

<RouteConfigComponent />

Renders the subroutes without adding any extra content.

import { RouteConfigComponent } from '@mamawosh/router-config';

const tree = [
  {
    path: '/level-1',
    component: RouteConfigComponent, // Should render level two without any additional content.
    routes: [
      {
        path: '/level-2',
        component: RouteConfigComponent, // Should render level three without any additional content.
        routes: [
          {
            path: '/level-3'
            component: LevelThree, // Renders a string which says "Level Three".
          },
        ],
      },
    ],
  },
];

Use of this component in other components is not encouraged, but is perfectly valid, as long as this component receives all values in RouteConfigComponentProps (which extends RouteComponentProps):

Here's a rough equivalent of RouteConfigComponentProps:

import * as H from 'history';

// NOTE:
// This is a rough equivalent of RouteConfigComponentProps<{}>.
// Always refer to the source materials for a better understanding!
interface RouteConfigComponentProps<Params extends { [K in keyof Params]?: string } = {}, C extends StaticContext = StaticContext, S = H.LocationState> {
  // "Params" refers to the kinds of parameters you're expecting the URL to provide, e.g. /:userId, /:addressId, etc.
  history: H.History;
  location: H.Location<S>;
  match: match<Params>;
  staticContext?: C;
  route?: RouteConfig; // Contains subroutes. See the `RouteConfig` sample earlier!
}

Here's how you can use RouteConfigComponent:

import React from 'react';
import { RouteConfigComponent, RouteConfigComponentProps } from 'react-router-config';


interface SomeComponentProps {
  // Your custom props here.
}

// Explicitly expect "route" props to be passed to this component.
// Wrap the existing "HelloProps" with "RouteConfigComponentProps" to apply the predicted values.
export const SomeComponent = function SomeComponent({
  history,          // Pass this value to <RouteConfigComponent>!
  location,         // Pass this value to <RouteConfigComponent>!
  match,            // Pass this value to <RouteConfigComponent>!
  staticContext,    // Pass this value to <RouteConfigComponent>!
  route,            // Pass this value to <RouteConfigComponent>!
  name,
}: RouteConfigComponentProps<SomeComponentProps>) {
  const routeConfigComponentProps = RouteConfigComponentProps<{}> = {
    history,
    location,
    match,
    staticContext,
    route,
  };

  return (
    <div>
      Hello, {name}!
      <RouteConfigComponent {...routeConfigComponentProps} />
    </div>
  );
}

We recommend that you simply use renderRoutes(routes?: RouteConfig[]) instead.

Develop

Install all dependencies

The /example folder uses Yarn, not NPM. We recommend that you install Yarn, too. When you run npm install, the scripts automatically download everything you need for both the library and its /example. If you need to run this yourself, read on.

Install all dependencies:

npm run install-all

This is the equivalent of...

npm install
cd example
yarn
cd ..

Run the example as you edit

To run the example, you'll need to build the distributable copy of the library's source code first as the example depends on the local copy, rather than the NPM registry copy. In order to make things easy, we made a convenience command available:

npm run develop

Every change you make & save to the source code will update the distributable copy in the /example/src/@mamawosh/router-config folder. Do not edit the code there.

In order to use this library in the example provided, import it like you would with a normal NPM module:

import { patchTreesRoutePath } from '@mamawosh/router-config';

const routes = patchTressRoutePath([
  // Write your implementations...
]);

Build

If you need a distributable copy for any particular reason, you can generate it through the source code.

Run the following command to create a distributable copy:

npm run build

The distributables will be produced in the /dist folder of your local copy of this library.

You can also tell the script to rebuild the distributables every time you edit & save your code:

npm run build-watch

However, for such cases, we recommend the Develop instructions as they require fewer steps.

Test

This library is tested using Jest.

Run the following command to test the code:

npm test

Publish

Update the version of the package using version-bump-prompt (see online instructions). Here's an example of a patch update in beta mode:

bump patch --beta

You can also update the versions manually, but it is not recommended.

Login & verify that the update shows no problems during the dry run:

npm login
npm publish --access public --dry-run

Run the following once all is good:

npm publish --access public

Package Sidebar

Install

npm i @mamawosh/router-config

Weekly Downloads

1

Version

0.1.0-beta.4

License

ISC

Unpacked Size

102 kB

Total Files

10

Last publish

Collaborators

  • andrewsantarin