Nearly Perfected Mystique

    This package has been deprecated

    Author message:

    Versions above 3.2.2 are deprecated. Downgrade to @3.2.2

    react-router-manager

    3.3.0-beta.0 • Public • Published

    react-router-manager

    React Router server side rendering with data fetching. This module is based on react-router-config and expands on the same concepts. The goal is to create a one stop solution for routing and data fetching on both client and server. It is particularly useful for people migrating from redial.

    Installation

    npm install --save react-router-manager
    

    Usage

    There are 2 distinct stages to working with this library, the first is data fetching and the second is rendering. In order for both to work you need to create "route configs". These configs allow for both routing and data fetching to work. Please use them for every route that needs to fetch data.

    IMPORTANT: the utilities here default to exact: true for route matching, which is the opposite of what Route components in React Router do. Partial matching is prone to errors and creates more issues than it solves. Override it on per route basis if needed.

    Route configs

    A route config is an array of objects like this:

    [
      // A Route
      {
        // The only difference with default Route paths is that you need
        // to use "*" for match-all data fetching and intercept functions.
        path: '/stuff/:id?',
    
        // This will be called both before the route data is fetched and before the route is rendered.
        // It allows you to completely rewrite the route object and return different components
        // or redirect conditionally. Return a new route configuration object here.
        // Anything except `path` and `intercept` can be returned (overriden).
        intercept: ({ match, location, route }) => {
          // Returning null will prevent fetching and rendering of the entire subtree.
          if (!isAuthorized()) return null;
    
          // Simply return a Route or a Redirect configuration object.
          if (match.params.id === '0') return { to: '/otherstuff' };
          return { component: MyComponent };
        },
    
        // These props will be passed on to the component and also available in the data fetching function
        props: {
          type: 'awesome',
        },
    
        // Component to render
        component: MyComponent,
    
        // You may create your own render function if you wish. It will receive the usual arguments
        // along with `children` which is the rendered nested routes for this route (or null).
        // NOTE: This way data fetching will not work for this route as it relies on `component` prop.
        render: ({ match, location, history, staticContext, children }) => {
          return <MyComponent>{children}</MyComponent>;
        }
    
        // Useful for handling 404s, but you can use it in any case.
        statusCode: 404,
    
        // You can nest routes like this.
        routes: [
          // {...}
        ],
    
        // Any other props you would pass to a Route
        // We default this to `true` unlike the React Router. Pass `false` to override.
        exact: false,
        // ...
      },
    
      // A redirect
      {
        // Set this statusCode for the redirect
        statusCode: 302,
    
        // Any other props you would pass to a Redirect
        from: '/this/:id',
        to: '/that/:id',
        // ...
      },
    
      // To use syntax like this also use a `.filter` function at the end
      myCondition && {
        from: '/old',
        to: '/new',
      },
    
      //...
    ]
    // Use this if you had any conditions, any falsy values in these arrays will get filtered out
    .filter(Boolean)

    Every route must have either component, render or intercept for rendering. For data fetching to work component is required. Everything else is optional.

    Connecting components

    To specify a data fetching function you need to create a static resolver method:

    // You can return either a promise or an array of promises
    MyComponent.resolver = ({ match, route }) => ([
      getMyAsyncDataAndReturnPromise(match.params, route.props.type),
      andAnotherPromise(match.params),
    ]);
    export default MyComponent;

    runResolver

    When you are ready to collect your promises and fetch the data use runResolver:

    import { runResolver } from './modules/resolver';
    const routes = [/* routes config */];
    const { location } = history;
    
    // By default each static resolver method gets { match, route, location } object as an argument.
    // You can modify that object here as you wish.
    const getLocals = (request) => ({ ...request, store: reduxStore })
    
    // You get back an array of promises to resolve as you will
    Promise.all(runResolver(routes, location, getLocals));

    You might want to use Promise.allSettled on the server side to wait for every promise to resolve or reject. Or you can go for a "fail early" approach with Promise.all everywhere. Returning an array gives you that choice.

    RouterManager or renderRoutes

    This simply renders the route configuration for you. This is the default export and you can use it as a function or as a component. Pass directly into React Router like this:

    import RouterManager from 'react-router-manager';
    import { Router } from 'react-router';
    import { createBrowserHistory } from 'history';
    import { hydrate } from 'react-dom';
    
    const routes = [/* routes config */];
    
    hydrate(
      <Router history={createBrowserHistory()}><RouterManager routes={routes} /></Router>,
      document.getElementById('root')
    );

    Alternatively:

    import renderRoutes from 'react-router-manager';
    // ...
    hydrate(
      <Router history={createBrowserHistory()}>{renderRoutes({ routes })}</Router>,
      document.getElementById('root')
    );

    Utility functions

    These are used internally, but you might want to use them too.

    injectStatusCode

    import { injectStatusCode } from 'react-router-manager';
    
    const MyComponent = (props) => {
      // Simply injects statusCode when static context is available. Use in any Component where
      // route information is available.
      injectStatusCode(props.staticContext, 403);
      //...
    };

    RouteStatus

    import { RouteStatus } from 'react-router-manager';
    
    const MyComponent = (props) => {
      return (
        // This is essentially the same as injectStatusCode but in a Route object form.
        <RouteStatus statusCode={403}>
          {/* content */}
        </RouteStatus>
      );
    };

    Route

    You can use it as a function or as a component.

    import { Route } from 'react-router-manager';
    
    // Will simply render a single route
    return <Route path="/stuff/:id?" component={MyComponent} />;

    Alternatively:

    import { Route as renderRoute } from 'react-router-manager';
    
    // Will simply render a single route
    return renderRoute({ path: '/stuff/:id?', component: MyComponent });

    Redirect

    You can use it as a function or as a component.

    import { Redirect } from 'react-router-manager';
    
    // Will simply render a single redirect
    return <Redirect from="/this/:id" to="/that/:id" />;

    Alternatively:

    import { Redirect as renderRedirect } from 'react-router-manager';
    
    // Will simply render a single route
    return renderRedirect({ from: '/this/:id', to: '/that/:id' });

    A complete example

    On the client side:

    import { render, hydrate } from 'react-dom';
    
    import { Router } from 'react-router';
    import { createBrowserHistory } from 'history';
    import RouterManager, { runResolver } from 'react-router-manager';
    
    import routes from './routes';
    
    
    // You may want to disable prerendering during development
    const isAppPrerendered = global.__APP_PRERENDERED__;
    
    const renderPage = (history, routes) => {
      const content = <Router history={history}><RouterManager routes={routes} /></Router>
    
      const renderer = isAppPrerendered ? hydrate : render;
      return renderer(content, document.getElementById('main'));
    };
    
    const startRouter = (store, history) => {
      let shouldFetch = !isAppPrerendered;
    
      const handleFetch = (location) => {
        if (shouldFetch) runResolver(routes, location);
        shouldFetch = true;
      };
    
      // This allows to fetch data after navigation. You may use a different strategy if you wish.
      history.listen(handleFetch);
      handleFetch(history.location);
    
      renderPage(store, history, routes);
    };
    
    // Call setup functions. First setup store, then initialize router.
    const history = createBrowserHistory();
    startRouter(store, history);

    An expressjs middleware on the server side:

    import { renderToString } from 'react-dom/server';
    
    import { StaticRouter } from 'react-router';
    import { createLocation } from 'history';
    import RouterManager, { runResolver } from 'react-router-manager';
    
    import routes from './client/routes';
    
    
    const renderContent = (routes, context, location) => {
      const content = (
        <StaticRouter {...{ context, location }}><RouterManager routes={routes} /></StaticRouter>
      );
      return renderToString(content);
    };
    
    export default (req, res) => {
      const location = createLocation(req.url);
    
      const matchPage = (result, error) => {
        const data = error ? {} : result;
        const context = {};
        const content = renderContent(routes, context, req.url);
    
        if (context.url) return res.redirect(context.statusCode || 301, context.url);
        // In your template serialize data into `__APP_PRERENDERED__`;
        res.status(context.statusCode || 200).render('index', { data, content });
      };
    
      const handleError = (error) => {
        console.error(`Request ${req.url} failed to fetch data:`, error);
        matchPage(null, error);
      };
    
      runResolver(routes, location).then(matchPage).catch(handleError);
    };

    Install

    npm i react-router-manager

    Homepage

    goodhood.eu

    DownloadsWeekly Downloads

    34

    Version

    3.3.0-beta.0

    License

    MIT

    Unpacked Size

    20.6 kB

    Total Files

    7

    Last publish

    Collaborators

    • zeitwidrig
    • iamuchejude
    • eliasbraun
    • peterhassler
    • ozerovs
    • suprmax