React Router 4 Gotchas
Many of you probably already knew, react-router
is now at version 4. It comes with several changes but unfortunately, the tutorials that are floating around in the internet today may not be necessarily up to date. This can be frustrating for some, well … it was for me. Thus I’m writing this blog, documenting the gotchas, I learnt along the way.
Different router for different environments
In version 4, there are two different kind of routers:
react-router-dom
to be used in a browserreact-router-native
to be used in areact-native
app
For me, I was building a static website so I used react-router-dom
import { HashRouter, Route, Switch } from 'react-router-dom'
This is actually pretty nifty, because if I want to port my code to react-native
I can just update the import
statement.
<Router> is gone
As you probably have noticed by now, <Router>
is gone. It is replaced by <HashRouter>
and <BrowserRouter>
. If you keep using <Router>
, you will see this as console warning
Warning: Failed prop type: The prop `history` is marked as required in `Router`, but its value is `undefined`.
So what are the new routers for?
<HashRouter>
, basically it uses the hash in the URL to render the component. Since I was building a static one-page website, I needed to use this.<BrowserRouter>
, it uses HTML5 history API to render the component. The history can be modified viapushState
andreplaceState
. More information can be found here
A <Switch> for exclusive multiple <Route>s
If your app have multiple routes like mine. You need to wrap the routes with <Switch>
<HashRouter>
<Switch>
<Route exact path="/" component={App} />
<Route exact path="/about" component={About} />
</Switch>
</HashRouter>
Without <Switch>
you probably would end up with the following error
Uncaught Error: A <Router> may have only one child element
If you google that, you might end up with a suggestion to put <div>
instead. This works fine and the error will be gone. However, this makes the routes inclusive, which means if a path can match two routes, both components will render.
Contrary to my initial thinking the syntax exact
in the <Route>
does not make the route exclusive, i.e. the router will still check the next rule instead of stopping.
So please use <Switch>
instead. For more information please read this
Passing parameters
To pass a parameter in, it is actually quite straightforward.
<Route exact path="/profile/:id" component={Profile} />
If you setup a route as the above, id
is then accessible as a String
in the Profile’s props
via match.params.id
. So in Profile.js
I can write something like
class Profile extends React.Component {
render() {
return (
<div>
<h1>Profile</h1>
<h2>{this.props.match.params.id}</h2>
</div>
);
}
}
So what is the gotcha?
I intended to put my one-page app in Github Pages or Amazon S3. Thus my app is considered as static. I was oblivious about the difference of BrowserRouter
and HashRouter
. I was under impression that using BrowserRouter
is the recommended way regardless if my react app is static or backed by a dynamic server.
The gotcha is, if I use BrowserRouter
, it will NOT take the hash url into account. Thus when I tried to access http://localhost:8080/#/profile/1, the router will just stop at the first rule i.e.
<Route exact path="/" component={App} />
Once I switched BrowserRouter
to HashRouter
, everything worked as normal.
Code Overview
For those who are interested, below is react routing code
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Route, Switch } from 'react-router-dom';import App from './App.js';
import About from './About.js';
import Post from './Profile.js';ReactDOM.render(
<HashRouter>
<Switch>
<Route exact path="/" component={App} />
<Route exact path="/about" component={About} />
<Route exact path="/profile/:id" component={Profile} />
</Switch>
</HashRouter>
, document.getElementById('app')
);
Note that I am using ES6
class approach which is recommended by React since version 0.13
That’s all folks!! Thanks for reading.