HashRouter

Version 4 of React-Router seperate top level router element for the different history type. If you’re using version 4 you should be able to replace <Router hisotry={hashHistory}> with <HashRouter>

Multiple Routes at same URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
} from 'react-router-dom'

// Each logical 'route' has two components, one for the sidebar and one for the main area. We want to render both of them in different places when the path matches the current URL.

const routes = [
{
path: '/',
exact: true,
sidebar: () => <div>Home</div>,
main: () => <h2>Home</h2>,
}, {
path: '/bubblegum',
sidebar: () => <div>bubblegum</div>,
main: () => <h2>BubbleGum</div>,
}, {
path: '/shoelaces',
sidebar: () => <div>shoelaces</div>,
main: () => <h2>ShoeLaces</h2>,
},
]

const sidebarExample = () => (
<Router>
<div style={{ display: 'flex' }}>
// sidebar
<div style={{
padding: '10px',
width: '40%',
background: '#f0f0f0',
}}
>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/bubblegum'>Bubblegum</Link></li>
<li><Link to='/shoelaces'>ShoeLaces</Link></li>
</ul>

{routes.map((route, index) => (
// You can render a <Router> in as many places as you want in your app, it will render along with any other <Router>s that also match the URL.
// So a sidebar or breadcrumbs or anything else that requires you to render multiple things in multiple places at the same URL is nothing more than multiple <Routes>s

<Route
key={index}
path={route.path}
exact={route.exact}
component={route.sidebar}
/>
))}
</div>

// main
<div style={{ flex: 1, padding: '30px' }}>
{routes.map((route, index) => (
// Render more <Route>s with the same paths as above, but different components this time.
<Route
key={index}
path={route.path}
exact={route.exact}
component={route.main}
/>
))}
</div>

</div>
</Router>
)
export default sidebarExample

Animated Transitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import React from 'react'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
} from 'react-rotuer-dom'

// you'll need this CSS somewhere
// .fade-enter {
// opacity: 0;
// z-index: 1;
// }
// .fade-enter.face-enter-active {
// opacity: 1;
// transition: opacity 250ms ease-in;
// }

const AnimationExample = () => (
<Router>
<Route render={({ location }) => (
<div style={style.fill}>
<Route exact path='/' render={() => (
<Redirect to='/10/20/50' />
)}/>
<ul style={style.nav}>
<NavLink to='/10/90/50'>Red</NavLink>
<NavLink to='/120/100/40'>Green</NavLink>
<NavLink to='/200/100/40'>Blue</NavLink>
<NavLink to='/310/100/50'>Pink</NavLink>
</ul>

<div style={style.content}>
<ReactCSSTransitionGroup
transitionName='fade'
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
>
// no different than other usage of ReactCSSTransitionGroup, just make sure to pass `location` to `Route` so it can match the old location as it animate out
<Route
location={loaction}
key={location.key}
path="/:h/:s/:l"
component={HSL}
/>
</ReactCSSTransitionGroup>
</div>
</div>
)}/>
</Router>
)

const NavLink = (props) => (
<li><Link {...props} style={{color: 'inherit'}} /></li>
)

const HSL = ({ match: { params }}) => (
<div style={{
...style.fill,
...style.hsl,
background: `hsl(${params.h}%, ${params.s}%, ${params.l}%)`
}}>
</div>
)

export default AnimationExample

Route Config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
} from 'react-router-dom'

// Some folks find value in a centralized route config. A route config is just data. React is great at mapping data into component, and <Route> is a component.

// first our route component

const Main = () => <h2>Main</h2>

const Sandwiches = () => <h2>Sandwiches</h2>

const Tacos = ({ routes }) => (
<div>
<h2>Tacos</h2>
<ul>
<li><Link to='/tacos/bus'>Bus</Link></li>
<li><Link to='/tacos/cart'>Cart</Link></li>
</ul>

{routes.map((route, index) => (
<RouteWithSubRoutes key={index} {...route}/>
))}
</div>
)

const Bus = () => <div>Bus</div>
const Cart = () => <div>Cart</div>

// then our route config

const routes = [
{
path: '/sandwiches',
component: sandwiches,
}, {
path: '/tacos',
component: Tacos,
routes: [
{
path: '/tacos/bus',
component: Bus,
}, {
path: '/tacos/cart',
component: Cart,
},
],
},
]

// wrap <Route> and use this everywhere instead, then when sub routes are added to any route it'll work

const RouteWithSubRoutes = (route) => (
<Route path={route.path} render={props => (
// pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes} />
)}/>
)

const RouteConfigExample = () => (
<Router>
<div>
<ul>
<li><Link to='/tacos'>Tacos</Link></li>
<li><Link to='/sandwiches'>Sandwiches</Link></li>
</ul>
{routes.map((route, index) => (
<RouteWithSubRoutes key={index} {...route} />
))}
</div>
</Router>
)

export default RouteConfigExample

Redux Integration

Generally, React Router and Redux work just fine together. Occasionally though, an app have a component that doesn’t update when the location changes (child route or active nav links don’t update)

This happens if:

The component is connected to redux via connect()(Comp) The component is not a “route component”, meaning it is not rendered like so: <Route component={SomeConnectedThing}/>

The problem is that Redux implements shouldComponentUpdate and there’s no indication that anything has changed if it isn’t receiving props from the router. This is straightforward to fix. Find where you connect your component and wrap it in withRouter

1
2
import { withRouter } from 'react-router-dom'
export default withRouter(connect(mapStateToProps)(Comp))

Code Splitting

One great feature of the web is that we don’t have to make our visitors download the entire app before they can use it. You can think of code spliting as incrementally downloading the app. While there are other tools for the job, react-router use webpack and the bundle loader.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import loadSomething from 'bundle-loader?lazy!./Something'

<Bundle load={loadSomething}>
{(mod) => (
// do something w/ the module
)}
</Bundle>

<Bundle load={loadSomething}>
{
(Comp) => Comp
? <Comp/>
: <Loading/>
}
</Bundle>

import React, { Component } from 'react'

class Bundle extends Component {
state = {
// short for 'module' but that's a keyword in js. so 'mod'
mod: null,
}

componentWillMount () {
this.load(this.props)
}

componentWillReceiveProps (nextProps) {
if (nextProps.load !== this.props.load) {
this.load(nextProps)
}
}

load (props) {
this.setState({
mod: null,
})
props.load((mod) => {
this.setState({
// handle both es imports and cjs
mod: mod.default ? mod.default : mod
})
})
}

render () {
return this.props.children(this.state.mod)
}
}

export default Bundle


<Bundle load={() => import('./something')}>
{(mod) => ()}
</Bundle>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import loadAbout from 'bundle-load?lazy!./loadAbout'
import loadDashboard from 'bundle-loader?lazy!./loadDashboard'

// components load their module for initial visit

const About = () => (
<Bundle load={loadAbout}>
{(About) => <About />}
</Bundle>
)

const Dashboard = () => (
<Bundle load={loadDashboard}>
{(Dashboard) => <Dashboard />}
</Bundle>
)

class App extends React.Component {
componentDidMount () {
// preload the rest
loadAbout( () => {} )
loadDashboard( () => {} )
}

render () {
return (
<div>
<h1>Welcome</h1>
<Route path='/about' component={About} />
<Route path='/dashbaord' component={Dashboard} />
</div>
)
}
}

ReactDOM.render(<App />, document.preloadTheRestOfTheApp)