Async React Components in React Router v4

Working example of pulling in components asynchronously into React Router v4 routes compatible with React Hot Loader v3 and server-side rendering

Kevin Ghadyani
4 min readNov 22, 2016

If you’re on React 16.6 or higher, please checkout my new article which explains the new best-practice way of doing this exact same thing.

React Router’s transition to v4 has completely changed the routing philosophy from one that works like everything else out there to one that works like a React component. It’s the future of routing in React, but the tough part is the change in feature set. Now that it’s been significantly simplified, some features you might have utilized in past versions will now have to be handled by hand. I’m going to show you how I solved the issue of server-side-compatible async routes using a single <Routes /> module.

A major complaint I’ve had with React Router’s transition to v4 is actually the loss of a major feature: async routes. Webpack’s chunking through require.ensure is a great way to reduce the initial download size of js files with a single entry point. In my use case, I have a bunch of static routes and want a simple way of defining the views in my app such as /, /about, /contact, etc.

Reference the full example project here:
https://github.com/Sawtaytoes/Ghadyani-Framework-Webpack-React-Redux/tree/Async_React_Router_v4_Components_with_React_Hot_Loader

Reasoning

React 15.4.x and higher won’t be optional sometime in the future, and the old React Hot Loader 1.3.0 is no longer compatible even though it’s a critical component in most Webpack + React setups. React Hot Loader 3 is the next compatible version. Sadly, it’s incompatible with older React Router versions so long as you want async routes. Because of this, React Router 4 is now going to be a necessary future upgrade and one that I’ll need to figure out sooner than later.

Goals

  • Asynchronously load components and feed them into <Match /> on route changes.
  • Also render the same async routes component server-side.
  • Allow the use of React Hot Loader v3.

Setup

  • react: 15.4.0
  • react-dom: 15.4.0
  • react-router: 4.0.0-alpha.6
  • webpack: 1.13.3

Writing the Code

Let’s first examine the brand new React Router 4 components we’re going to use:

import React from 'react'
import { Match, Miss, Redirect } from 'react-router'
<Match pattern="/" exactly component={<div />}>
<Miss component={<div />} />
<Redirect to="/" />

<Match /> will match on a route and load a component.

For instance:

<Match pattern="/" exactly component={<div />} />

<Redirect /> only works with Match so you’ll have to use them in conjunction like so:

<Match
pattern="/redirect-test-link"
component={<Redirect to="/" />}
/>

While these are pretty simple examples, they set the foundation for grabbing async routes. The component property on Match needs a React for rendering; it could be any React component. After we load one in there, we can change it up and after the Routes component has loaded, we simply and force a reload using this.forceUpdate().

Knowing this, we can use require.ensure from Webpack, wait for it to get a response, and trigger our update.

Simple Example

We can exploit the fact that Match will call its component property as a function and bind an identifier to it. When it’s called in this way, we’ll have 'home' available to us. Once we’re in loadView(), we skip the first condition and move onto the creation of a promise which loads our file asynchronously thanks to Webpack’s require.ensure.

While that’s happening in the background, we’ll return a blank<div />to Match so we can prevent an error from not giving it a component at render time. At this point, Match is completely unaware of our sneaky setup.

When we finally get our component loaded, we’ll load the component into a storage container so we don’t have to load it again. Last step in the process is triggering this.forceUpdate() to re-render the component. When loadView()is called again, this time we’ll early-return from the function with our <View /> component from storage. Match will receive the component and render it instead of the blank <div /> we gave it before.

That’s all there is to it! 😃

But what about server-side compatibility?

To get this working on the server, we’ll need to first check if we’re not in a browser environment — typeof window === ‘undefined’— then return the view right after requiring it in instead of loading it in dynamically.

const View = require(`./views/${fileName}`)
return <View />

Complex Example

Here’s an example of a more-complex solution including server-side rendering capabilities:

There’s a lot more going on in this example, but the main changes are the loading of routes and redirects dynamically. Set them in the constructor, and it’ll generate them for you similar to a routes file from previous versions of React Router.

React Router sure has gone through a lot of API changes and this latest one is a major change-around. Hopefully this guide has helped return an important missing feature back to your React Router arsenal.

[EDIT]

React Hot Loader Warning

When using this in conjunction with React Hot Loader, ensure you’re keeping your <Root> or <App> component as a React Component instead of PureComponent. I was bit by this and spent many hours of trial and error getting it working.

[EDIT 2]

React 16.6+

If you’re on React 16.6 or higher, please checkout my new article which explains the new best-practice way of doing this exact same thing.

--

--