React Router v5
Learning Goals:
- Understand and articulate the need for routing
- Be able to confidently implement React Router in a project
- Utilize URL params to build dynamic routes
Vocab
BrowserRouter
A <Router> that uses the HTML5 history API to keep your UI in sync with the URLRouter
The class that <BrowserRouter> is extended fromRoute
Its most basic responsibility is to render some UI when a location matches the route’s pathLink
Links provide declarative, accessible navigation around your applicationNavLink
A special version of the <Link> that will add styling attributes to the rendered element when it matches the current URL.Redirect
Rendering a <Redirect> will navigate to a new location. The new location will override the current location in the history stack, like server-side redirects (HTTP 3xx) do.Switch
Renders the first child <Route> or <Redirect> that matches the location. <Switch> is unique in that it renders a route exclusively (only one route wins).match
A match object contains information about how a <Route path> matched the URL.
Prework
Before the lesson, complete the prework.
React Router
Prework Review
In small groups, discuss the following questions:
- Why use Router?
- Describe the high-level process of setting up Router in a project (packages to install, basic component needed)
- Describe the following components:
- Route
- Redirect
- Link
- NavLink
- Switch
Why Routing?
Routing refers to keeping a webpage up to date with the current URL, and vice-versa.
Most of the apps you’ve written so far have been single-page applications. One HTML page whose content is updated through user interactions and JS. These DO NOT use routing.They work fine, but put some limits on the user experience of our applications.
What are some advantages routing can provide?
- Users can use URLs to bookmark pages
- Users can use the back or forward button
- Users can easily share content from a page in the app
If you have written a multi-page application, you may have wrestled with Webpack configs in order to get all your pages built successfully. Fortunately, routing with React is easier than managing Webpack configuration! We just need to use a library called React Router.
The Code
Rather than tell you about how Router works, we’ll work through a series of exercises and examples.
We’ll be using this repo to solve a series of challenges listed below.
Installation Instructions
git clone https://github.com/turingschool-examples/react-router-v5
cd react-router-v5
npm i
npm start
Code Sand Box Template
You’re also welcome to use the code sand box template, found here.
The App is not fully put together. It has a series of components that will serve as building blocks of the final component. You won’t be building out new components, but you will be editing existing ones.
Setting up Router
Before we break out into groups, we’ll review how to set up Router as a class.
Look through the codebase
Get oriented with the application. Check out all the components, try and write a short summary of what each is doing.
The <Home />
component is rendering a welcome message. Right now, nothing but a nav bar is being rendered by the App. Let’s use router to render the <Home />
component as a landing page.
Remember that React Router conditionally renders components based on the current URL. So our goal is to render the
Getting Started
To use React Router, we need to wrap any components that will use a React-Router-provided-component with a Router component.
We’ll use a Browser Router component because our app will be used in the browser. This Router provides access to the HTML5 History API. But we won’t worry about those details just yet.
The first step, installing React Router
npm install react-router-dom@5.3.0
Router Versioning
The current version of React Router is version 6. In the installation step above, you installed version 5. Version 6 of React Router is very different from version 5, and we are learning version 5 in this lesson.
So whenever you setup React Router in your projects, be sure to use this exact installation command (npm install react-router-dom@5.3.0
). If you use npm install react-router-dom
without the @5.3.0
then version 6 will be installed by default, and that will cause a lot of confusion because React Router will not behave as expected for you.
Once React Router is installed, import the chosen Router
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App/App';
import { BrowserRouter } from 'react-router-dom';
const router = <BrowserRouter> <App /> </BrowserRouter>;
ReactDOM.render(router, document.getElementById('root'));
Finally, add a Route for the Home
component into your App
component
import React, { Component } from 'react';
import './App.css';
import puppies from '../data/puppy-data.js';
import sharks from '../data/shark-data.js';
import Creatures from '../Creatures/Creatures';
import Home from '../Home/Home';
import CreatureDetails from '../Creatures/CreatureDetails';
import { Route } from 'react-router-dom';
export default class App extends Component {
render() {
return (
<main className="App">
<nav>
<a href="/puppies" className="nav">Puppies</a>
<a href="/sharks" className="nav">Sharks</a>
</nav>
<h1>Puppies or Sharks?</h1>
<Route path="/" component={ Home }/>
</main>
);
}
}
We picked /
for the path in the route because it designates that there won’t be anything after the URL’s domain name. This represents the base URL.
Exercise # 1: Render Puppies
Your goal is click on the word Puppies and see a grid of 9 puppies on the DOM. The page should look something like the picture on the lesson plan. While you may change components as needed, you shouldn’t outright delete content from the page to achieve this.
Take 10 minutes in pairs to get the puppies rendering
Hints:
- Use the Creatures component. Formatting and styling is handled for you.
- What additional react-router components should you use? Do any current components need to change?
- How do you pass props into a component rendered by a
<Route />
?
Solution
/ App.js
import './App.css';
import puppies from '../data/puppy-data.js';
import sharks from '../data/shark-data.js';
import Creatures from '../Creatures/Creatures';
import Home from '../Home/Home';
import { Route, NavLink } from 'react-router-dom';
export default class App extends Component {
render() {
return (
<main className="App">
<nav>
<NavLink to="/puppies" className="nav">Puppies</NavLink>
<NavLink to="/sharks" className="nav">Sharks</NavLink>
</nav>
<h1>Puppies or Sharks?</h1>
<Route path="/" component={ Home }/>
<Route path="/puppies" render={() => <Creatures name="puppies" data={puppies} />} />
</main>
);
}
}
Solo Reflection
Why do you think the Home
page is rendering at /puppies
?
Render exact
matches
Check out what happens when you add the exact
prop into one of your Routes.
The exact
prop can be used to make sure that partial matches of a URL don't trigger a render.
Documentation Versioning
As with the installation step, we need to be mindful about which version of documentation we are looking at. Since we installed version 5 of React Router, we need to view the documentation for version 5 as well.
This link will take you to the version 5 React Router documentation. Note the v5
in the URL.
Render Methods
According to the docs, Routes have three possible methods for rendering a component on match:
component
render
children
We recommend to use render
or children
– they work more efficiently when re-rendering components. We’ll take a look at some more benefits they provide after the next exercise.
Here’s an example of their syntax: Component
<Route path='/unicorns' component={ Unicorns } />
Render
<Route path='/unicorns' render={ () => <Unicorns /> }
This also allows you to define and pass specific properties to a component dynamically. For example:
<Route path='/ideas/:id' render={({ match }) => {
const idea = ideas.find(idea => idea.id === parseInt(match.params.id));
if (!idea) {
return (<div>This idea does not exist! </div>);
}
return <ListItem match={match} {...idea} />
}} />
Children
children
works exactly like render
except that it gets called whether the path is a match or not.
<Route path='/other-unicorns' children={ () => <Unicorns /> } />
Exercise # 2: Rendering Sharks
Get the sharks link working as well!
Solution
// App.js
import './App.css';
import puppies from '../data/puppy-data.js';
import sharks from '../data/shark-data.js';
import Creatures from '../Creatures/Creatures';
import Home from '../Home/Home';
import { Route, NavLink } from 'react-router-dom';
export default class App extends Component {
render() {
return (
<main className="App">
<nav>
<NavLink to="/puppies" className="nav">Puppies</NavLink>
<NavLink to="/sharks" className="nav">Sharks</NavLink>
</nav>
<h1>Puppies or Sharks?</h1>
<Route exact path="/" render={ Home }/>
<Route exact path="/puppies" render={() => <Creatures name="puppies" data={puppies} />} />
<Route exact path="/sharks" render={() => <Creatures name="sharks" data={sharks} />} />
</main>
);
}
}
Route Props
Let’s take a close look at what happens when a Route renders.
Route render methods all provide access to route props, either automatically to the component they render, or via the callback function that the methods take.
These props include:
<Route path='/unicorns' render={ ({ history, location, match }) => <Unicorns /> }
history and location are worth looking into on your own, but today we’ll focus on match
.
The match
gives us information about how and why the application matched. And it allows us to do some pretty cool stuff.
Solo Exploration
Add this to your code:
<Route path="/puppies" render={({ match }) => { console.log(match)}} />
- What is logged in the console when you go to the puppies page?
- Look at the
params
property. What do you see?
Now change the line of code above to this:
<Route path="/:animal" render={({ match }) => { console.log(match)}} />
- What do you notice about the value for the
params
propery? Where is each piece of that key:value pair coming from?
match.params
The params
property of the match prop gives us an object with key value pairs of dynamic URL parameters, and any strings that match them.
For instance, we could make our routes for animals more dynamic by doing this:
<Route
exact path="/:animal"
render={({ match }) => {
const whichAnimal = match.params.animal === 'sharks' ? sharks : puppies
return <Creatures name={match.params.animal} data={whichAnimal} />
}}
/>
and then navigate to either /puppies
or /sharks
, we can see that the <Creatures />
component is rendering the correct data based on the params
from the URL.
params
allows us to define shapes of a URL that will cause a match, then access the data from that URL in our components.
This can be great for dynamically rendering content based on things in the URL, like an id. Let’s do that!
Exercise #3: Dynamic Routing
Take a look at the CreatureDetails Component. It takes in all data for a given creature, and displays it on the page.
Your Task is to make a route that will dynamically render a CreatureDetails component for a puppy based on its ID.
For example, the URL /puppies/1
should render a view just for the puppy with an ID of 1 in the dataset, with all of its details (name, bio, etc)
Hints:
- Use the CreatureDetails component
- What will you need to do to ensure that the URL is updated when you click on a given puppy?
- How can you access the id from the URL?
- How can you find a one puppy’s data in an array based on its id?
Solution
It could look something like this:
// **CreatureDetails.js**
// ...
return (
<NavLink to={`/${name}/${id}`}>
<img src={image} key={id} id={id} className="app-img"/>
</NavLink>
)
// ...
// **App.js**
// ...
<Route
exact path="/puppies/:id"
render={({match}) => {
const creatureToRender = puppies.find(creature => creature.id === parseInt(match.params.id));
return <CreatureDetails {...creatureToRender} />
}}
/>
// ...
Extra Resources:
Tutorials / Guides:
- React Training’s 13 minute overview of React Router
- A Beginner’s Tutorial - 5 Minutes
- Router Cheatsheet
Helpful Articles / Docs:
Check out this additional information on some Router Components:
Link
Provides declarative, accessible navigation around your application.
Things to know:
- Link can contain an open and closing tag or be a self-closing tag
- Link takes a
to
attribute as well as an optionalreplace
attribute to
tells the app which path to redirect to. This can be a string or an objectreplace
is a boolean that whentrue
will replace the current entry in the history stack instead of adding a new one
<Link to='/unicorns' />
<Link to='/unicorns'> Unicorns </Link>
NavLink
A special version of the <Link>
that will add styling attributes to the rendered element when it matches the current URL.
It can take the following attributes:
- activeClassName: string - defaults to
active
- activeStyle: object
- exact: bool
- strict: bool
- isActive: func
- location: object
Read about each of these here
<NavLink to='/about'>About</NavLink>
Redirect
Rendering a <Redirect>
will navigate to a new location. The new location will override the current location in the history stack, like server-side redirects (HTTP 3xx) do.
More of a nice to know for now. This is something that can be used if the user does something wrong. ie. went to a route they don’t have permissions to access.
It can take the following attributes:
- to: string
- to: object
- push: bool
- from: string
<Redirect to='/not/unicorns' />
Switch
Renders the first child <Route>
or <Redirect>
that matches the location. <Switch>
is unique in that it renders a route exclusively (only one route wins). In contrast, every <Route>
that matches the location renders inclusively (more than one route can match and render at a time)
<Switch>
<Route exact path='/' component={Home} />
<Route path='/users/add' component={UserAddPage} />
<Route path='/users' component={UsersPage} />
<Redirect to='/' />
</Switch>
The docs do a great job of quickly showing what Switch is all about.