Skip to content

Instantly share code, notes, and snippets.

@simonedavico
Created January 17, 2017 15:48
Show Gist options
  • Save simonedavico/f186e575b616da23fb29d1fbea258d3f to your computer and use it in GitHub Desktop.
Save simonedavico/f186e575b616da23fb29d1fbea258d3f to your computer and use it in GitHub Desktop.
Decorating async route definitions from react-redux-starter-kit (React+React-Router+Redux and Webpack code splitting)

Context

Today I was working on a project in which I use React, React-Router and Redux for the front-end. I also use webpack to build the project, and, since I need dynamic routing, I employ require and require.ensure to exploit Webpack's [https://webpack.github.io/docs/code-splitting.html](code splitting feature). I borrowed this idea from react-redux-starter-kit, and for some use cases it is awesome. A dynamic route looks like this:

const SomeRoute = (store) => ({
  path: 'someroute',
  /*  Async getComponent is only invoked when route matches   */
  getComponent (nextState, cb) {
    /*  Webpack - use 'require.ensure' to create a split point
        and embed an async module loader (jsonp) when bundling   */

    require.ensure([], (require) => {

      /*  Webpack - use require callback to define
          dependencies for bundling   */
      const RouteContainer = require('./containers/RouteContainer').default;
      const reducer = require('./reducer').default;

      //add the reducer to the store on key 'someKey'
      injectReducer(store, { key: 'someKey', reducer });

      /*  Return getComponent   */
      cb(null, RouteContainer);

    /* Webpack named bundle   */
    }, 'bundleName')
  }
});

I was testing the routing on a slow network and noticed that, since the bundles are downloaded only when they are needed, the user experience was sluggish and conveyed the idea of the page being broken and not responding. I wanted to give the user some kind of feedback about the fact that something is loading and the page is not stuck. I also wanted to be able to add this feature easily to my code, without having to change every single route definition (I have several).

Since these routes are just React-Router Route objects, it is quite easy to manipulate them to address my needs. In fact, I wrote a decorator function that takes an existing async route definition, and calls two custom-defined onStart and onComplete event at the right times in the bundle loading lifecycle.

This is the whole code:

const decorateCb = beforeCb => cb => (
  (a,b) => {
    beforeCb();
    cb(a,b);
  }
);

export const decorateAsyncRoute = ({ onStart, onComplete }) =>
  (asyncRoute) => (store) => {

    const route = asyncRoute(store);

    return {
      ...route,
      getComponent: (nextState, cb) => {
        onStart(store);
        return route.getComponent(
          nextState,
          decorateCb(() => onComplete(store))(cb)
        )
      }
    }

  };

decorateAsyncRoute is quite simple, yet quite powerful. It allows to define custom onStart and onComplete callbacks, and can decorate any async route that implements getComponent.

For example, by exploiting react-redux-loading-bar, I created a decorator that automatically shows and hides a loading bar while the bundles are fetched:

import { showLoading, hideLoading } from 'react-redux-loading-bar'

export const withLoadingFeedback = (asyncRoute) =>
  decorateAsyncRoute({
    onStart: (store) => { store.dispatch(showLoading()) },
    onComplete: (store) => { store.dispatch(hideLoading()) }
  })(asyncRoute);

This can be enabled by calling withLoadingFeedback and passing an async route: export default withLoadingFeedback(SomeRoute).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment