-
Notifications
You must be signed in to change notification settings - Fork 1
Routing
- navigate within an SPA
- history API (back button)
In a Single Page Application, routing is simply a system for choosing what pieces of an application to render. Ideally this is kept inline with the browser's address bar and history API.
example:
// simple routing
render () {
switch (window.location.pathname) {
case '/':
return <h1>Home</h1>
case '/foo':
return <h1>Foo Page</h1>
default:
return <h2>page not found</h2>
}
}
React-Router V4 provides a helpful abstraction, and gives us a few nice features to make things a little easier.
Start by installing react-router.
yarn add react-router-dom@next
In RRV4 you use a Route
component to link a component
to a path
in our app:
./src/components/app/app.js
import { Route } from 'react-router-dom'
...
<Route path="/" component={RobotFilterViewContainer} />
<Route path="/profile" component={RobotProfileViewContainer} />
But before this will work, the routes must be nested under a Router
component.
in our app:
src/index.js
import { BrowserRouter as Router } from 'react-router-dom'
...
<Router>
<App/>
</Router>
src/index.dev.js
import { BrowserRouter as Router } from 'react-router-dom'
...
<Router>
<Component />
</Router>
At this point, the application should work and the RobotProfileViewContainer
will appear when we add /profile
to the address bar.
Unfortunately, RobotFilterViewContainer
also renders when we navigate to /profile
. By default RRV4 renders a component if the provided path
matches the beginning of the address (this is necessary for nested routes).
-
"/"
matches"/"
-
"/"
also matches"/profile"
We have to add the exact
attribute to the Route
component if we only want it to match "/"
in our app:
src/components/app/app.js
<Route path="/" exact component={RobotFilterViewContainer} />
The robot profile page currently only displays the robot with an id
of 1
, but we want a separate page for each individual robot. We don't have to define separate routes, we can accomplish this by defining url parameters in our paths and reading from the props passed down by RRV4.
First define a url param on the path by prepending a segment of the path with a :
in our app:
<Route path="/profile/:id" component={RobotProfileViewContainer} />
A Route element always passes a match
object to it's component
.
in our app:
src/containers/profile-view.container.js
const mapStateToProps = (state, ownProps) => {
console.log(ownProps.match)
/*
{
isExact: true,
params: Object,
path: "/profile/:id",
url: "/profile/3",
}
*/
The params
object on match
will contain a key for each url param defined in the path
example:
<Route path="/:foo/:bar/:baz" component={SomeComponent} />
...
// props.match.params
{
foo: String,
bar: String,
baz: String,
}
// param values are always strings
Now we can use the id from the address bar to display the correct robot.
in our app:
src/containers/profile-view.container.js
const mapStateToProps = (state, ownProps) => {
const id = parseInt(ownProps.match.params.id, 10) || 1
We now have working routes, but no way to navigate to different routes within our app. We could use html a
tags, but they would cause the page to reload when clicked. RRV4 provides a Link
component that allows us to avoid page reloads.
in our app:
src/components/card.js
import { Link } from 'react-router-dom'
...
<Link to={`/profile/${id}`}>
<div className="grow bg-light-green br3 pa3 ma2 dib">
<img alt={name} src={`//robohash.org/${id}?size=200x200`} />
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
</div>
</Link>
The address bar updates and you can even use the back button on the browser, but the page does not reload.
Let's add a back button to the profile page for convenience.
in our app:
src/components/profile/profile-view.js
import { Link } from 'react-router-dom'
...
<Link className="button" to="/">Back</Link>
We may want to handle routes that we haven't explicitly defined (404 page), to do this we need a catch-all route that will not render unless no other routes match.
RRV4 provides a Switch
component that will render the first child route with a path that matches and ignore the rest.
Because a route without a path
defined matches all paths, adding a path-less route as the last child element of a switch will allow us to define an element to render when no routes match.
example:
<Switch>
<Route path="/" exact component={HomeComponent} />
<Route path="/about" component={AboutComponent} />
<Route component={FourOhFourComponent} />
</Switch>
The RRV4 API, at first, seems limiting. Passing a component to the component
attribute doesn't allow you to pass down any props, or check a condition before rendering the component.
Alternately, you can omit component
and pass a function to the render
attribute. Within the function you can perform any actions or checks you need. The return value of the function is what will be rendered.
example:
<Route path="/privelegedRoute" render={(props) => {
if (!authenticated) {
return <h2>Permission Denied</h2>
} else {
return <PrivilegedComponent {...props} />
}
}}/>
It also allows you to return JSX, if you don't want to define an entire component, for something simple.
in our app:
src/components/app/app.js
import { Route, Switch } from 'react-router-dom'
...
<Switch>
<Route path="/" exact component={RobotFilterViewContainer} />
<Route path="/profile/:id" component={RobotProfileViewContainer} />
<Route render={() => <h2>Page not found</h2>} />
</Switch>
Sometimes a redirect is more usefull than a '404' or a 'permission denied' message. RRV4's Redirect component makes this simple. Just pass an object with a pathname
to the Redirect
component's to
attribute.
example:
<Route path="/privelegedRoute" render={(props) => {
if (!authenticated) {
return <Redirect to={{ pathname: '/login' }} />
} else {
return <PrivilegedComponent {...props} />
}
}}/>
in our app:
src/components/app/app.js
import { Route, Switch } from 'react-router-dom'
...
<Switch>
<Route path="/" exact component={RobotFilterViewContainer} />
<Route path="/profile/:id" component={RobotProfileViewContainer} />
<Redirect to={{ pathname: '/' }} />
</Switch>