Skip to content

Routing

Michael Faust edited this page Jun 5, 2019 · 3 revisions

Purpose of 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

Route

in our app:

./src/components/app/app.js

import { Route } from 'react-router-dom'
...

<Route path="/" component={RobotFilterViewContainer} />
<Route path="/profile" component={RobotProfileViewContainer} /> 

Router

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} />

URL params

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

Link

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>

Switch

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>

Render

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>

Redirect

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>
Clone this wiki locally