A gentle introduction to React Router 4 through practical examples.
TL;DR: React Router 4 is a body of navigational components that offers declarative routing in your React apps. In this tutorial, you are going to learn how to use React Router 4 through practical examples.
Routing is of uttermost importance in almost every application's architecture. The larger your app becomes, the more your routing functionality becomes complex, from simple to deeply nested routing scenarios.
React Router is the most popular and commonly used library for routing in React applications. As your application grows to require several views and routes, it's ideal you choose a good router to help manage the transition between views, redirects, getting URL parameters easily, et al.
"React Router is the most popular and commonly used library for routing in React applications. "
TWEET THIS
![]()
Before now, previous versions of React Router involved declaring your app's routes upfront, declaring all the routes in a file as part of your app's initialization before rendering occurs. With React Router 4, you get to route declaratively. React Router 4's API is basically just components thus making it easy to use if you already compose components in React. Let's dive in!
Setup and Installation
You'll need:
- Node.js (version 6.0 or greater) and npm.
- create-react-app for bootstrapping a new project.
React Router is composed of these packages: react-router
, react-router-dom
, and react-router-native
.
- react-router: comprises of the core routing components.
- react-router-dom: comprises of the routing API required for browsers.
- react-router-native: comprises of routing API for mobile applications.
Create a new project with create_react_app and navigate to the directory created as shown below:
create-react-app bose
cd bose
Install react-router-dom
.
npm install --save react-router-dom
What we'll cover
We'll focus on using React Router 4 for the browser. We'll cover the very important concepts listed below:
- Basic Routing
- Nested Routing and URL Parameters
- Route Protection and Authentication
- Link Component Customization
- Handling Non-existent Routes
- SideBar Rendering
Basic Routing
There are two types of Router components that you can use in your React web application. The BrowserRouter
and HashRouter
. The former gives you a URL without the #
, while the latter gives you a URL with the #
.
Note: If you are building a web application that supports legacy browsers, it's recommended that you use the
HashRouter
.
Open up your src/index.js
and add the code below to it:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(
<Router>
<App />
</Router>, document.getElementById('root'));
registerServiceWorker();
In the code above, I imported the BrowserRouter
, Route
, and Link
component from react-router-dom
. And I wrapped the <App/>
component with Router
which is the alias of BrowserRouter
. The Router component is the first step to routing successfully. It serves as the container for every other route component. Furthermore, the Router component can only have one child element or component. Now, how do we define our routes?
Open up src/App.js
. Here, we will define our routes.
import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';
import './App.css';
const Home = () => (
<div>
<h2> Home </h2>
</div>
);
const Airport = () => (
<div>
<ul>
<li>Jomo Kenyatta</li>
<li>Tambo</li>
<li>Murtala Mohammed</li>
</ul>
</div>
);
const City = () => (
<div>
<ul>
<li>San Francisco</li>
<li>Istanbul</li>
<li>Tokyo</li>
</ul>
</div>
);
class App extends Component {
render() {
return (
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/airports">Airports</Link></li>
<li><Link to="/cities">Cities</Link></li>
</ul>
<Route path="/" component={Home}/>
<Route path="/airports" component={Airport}/>
<Route path="/cities" component={City}/>
</div>
);
}
}
export default App;
In the code above, we have links that should direct the user to /
, /airports
, and cities
using the <Link>
component. Each of these links has a component that should be rendered once the current location matches the route's path. However, something is off here. Let's check the results.
Airports route
Home
which is the UI for Home
component should be rendered only on the /
, root route. However, it is rendered on all the routes. The /
matches /airports
and /cities
routes, therefore rendering its component in these two other routes. The solution to this is to simply add the exact
prop to the /
route.
src/App.js
<Route path="/" exact component={Home}/>
<Route path="/airports" component={Airport}/>
<Route path="/cities" component={City}/>
The Airports route without rendering Home component UI
In the examples above, all the <Route />
components have a component
prop that renders a component when the URL visited matches the Route's path. What if you just want to render a small function instead of a whole component? You can use the render
prop as shown in the code below.
<Route path="/airports"
render={() => (<div> This is the airport route </div>)}/>
Nested Routing & URL Parameters
What if you needed URLs like /courses/business
, and /courses/technology/
? How would you accomplish this?
src/App.js
import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';
import './App.css';
const Courses = ({ match }) => (
<div>
<ul>
<li><Link to="/courses/technology">Technology</Link></li>
<li><Link to="/courses/business">Business</Link></li>
<li><Link to="/courses/economics">Economics</Link></li>
</ul>
<Route exact path="/courses/technology" render={() => (<div> This is technology </div>)}/>
<Route path="/courses/business" component={() => (<div> This is business </div>)}/>
<Route path="/courses/economics" component={() => (<div> This is economics </div>)}/>
</div>
);
/* Home Component */ // code hidden
/* City Component */ //code hidden
class App extends Component {
render() {
return (
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/courses">Courses</Link></li>
<li><Link to="/cities">Cities</Link></li>
</ul>
<Route path="/" exact component={Home}/>
<Route path="/courses" component={Courses}/>
<Route path="/cities" component={City}/>
</div>
);
}
}
export default App;
If the URL location matches the /courses
path, then the technology, business, and economics links are rendered via the Courses
component. Going one step further, if the URL location matches /courses/technology
, /courses/business
, and /courses/economics
path, then This is technology
, This is business
, and This is economics
are rendered respectively.
As a developer, I'm sure you are already looking at this approach with a set of refactoring eyes. In the code sample above, there's a lot of repetition and hardcoding. The more the lines of code, the harder it becomes to change a route. Let's refactor.
React Router 4 ships with a match
API. The match
object is created when a router's path and URL location successfully matches. The match
object has some properties but I'll outline the properties you should immediately know about:
- match.url: returns a string that shows the URL location. Used for s
- match.path: returns a string that shows the route's path. Used for
s - match.params: returns an object with values parsed from the URL.
Let's refactor step by step. Refactor the Courses component to have the match
object like so:
const Courses = ({ match }) => (
<div>
<ul>
<li><Link to={`${match.url}/technology`}>Technology</Link></li>
<li><Link to={`${match.url}/business`}>Business</Link></li>
<li><Link to={`${match.url}/economics`}>Economics</Link></li>
</ul>
<Route exact path="/courses/technology" render={() => (<div> This is technology </div>)}/>
<Route path="/courses/business" component={() => (<div> This is business </div>)}/>
<Route path="/courses/economics" component={() => (<div> This is economics </div>)}/>
</div>
);
Test if your URLs are working. Now do the same for the routes but with match.path
.
const Courses = ({ match }) => (
<div>
<ul>
<li><Link to={`${match.url}/technology`}>Technology</Link></li>
<li><Link to={`${match.url}/business`}>Business</Link></li>
<li><Link to={`${match.url}/economics`}>Economics</Link></li>
</ul>
<Route exact path={`${match.path}/technology`} render={() => (<div> This is technology </div>)}/>
<Route path={`${match.path}/business`} component={() => (<div> This is business </div>)}/>
<Route path={`${match.path}/economics`} component={() => (<div> This is economics </div>)}/>
</div>
);
Check your app. Everything should work fine. Now one last step. We can actually replace those three lines of <Route>
code with just one line.
const Courses = ({ match }) => (
<div>
<ul>
<li><Link to={`${match.url}/technology`}>Technology</Link></li>
<li><Link to={`${match.url}/business`}>Business</Link></li>
<li><Link to={`${match.url}/economics`}>Economics</Link></li>
</ul>
<Route exact path={`${match.path}/:course`} render={({match}) => (<div> This is {match.params.course} </div>)}/>
</div>
);
We used the match.params
which provides a key/value object of the URL location. :course
is the URL param. Therefore, match.params.course
will provide the value of the correct URL location. Awesome!
Route Protection and Authentication
When developing web applications, there are scenarios where certain routes have to be protected from access. Most times, these routes can only be accessed by authorized users.
In previous versions of React Router such as v3, route protection code looks like this:
index.js
const Root = () => {
return (
<div className="container">
<Router history={browserHistory}>
<Route path="/" component={Display}/>
<Route path="/upload" component={Upload} onEnter={requireAuth} />
<Route path="/callback" component={Callback} />
</Router>
</div>
)
}
The <Route/>
component had a onEnter
prop that accepts a method that allows entry or refusal to a URL location based on a user's authentication status. Now, it's different for React Router 4.
Let's build out three components, Public
, Private
, and Login
.
App.js
import React, { Component } from 'react';
import {
Route,
Link,
BrowserRouter as Router,
} from 'react-router-dom';
const Public = () => (
<div> This is a public page </div>
);
const Private = () => (
<div> This is a private page </div>
);
const Login = () => (
<div> Login Page <button>login</button> </div>
);
class App extends Component {
render() {
return (
<Router>
<div style={{width: 1000, margin: '0 auto'}}>
<ul>
<li><Link to='/public'> Public </Link></li>
<li><Link to='/private'> Private </Link></li>
</ul>
<hr/>
<Route path='/public' component={Public} />
<Route path='/private' component={Private} />
</div>
</Router>
);
}
}
export default App;
Right now, we can access both routes, /public
, and /private
. Now, let's make sure the /private
route can't be accessed until a user is logged in. React Router 4 uses a declarative approach, so it's convenient that we have a component such as <SecretRoute />
that we can use. However, the react router 4 library doesn't provide it. We'll build it. But let's come up with an Auth Service.
In this example, the Auth Service will simply be an object like so:
const AuthService = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100)
},
logout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
Now, let's build the <SecretRoute />
like so:
const SecretRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
AuthService.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/login' />
)} />
);
SecretRoute component
The code above simply illustrates that if the authentication status of the user is true, then a component would be rendered else the user would be redirected to the /login
route. Let's try it out.
App.js
import React, { Component } from 'react';
import {
Route,
Link,
Redirect,
BrowserRouter as Router,
} from 'react-router-dom';
const Login = () => (
<div> Login Page <button>login</button> </div>
);
const AuthService = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100)
},
logout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
};
const SecretRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
AuthService.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/login' />
)} />
);
class App extends Component {
render() {
return (
<Router>
<div style={{width: 1000, margin: '0 auto'}}>
<ul>
<li><Link to='/public'> Public </Link></li>
<li><Link to='/private'> Private </Link></li>
</ul>
<hr/>
<Route path='/public' component={Public} />
<SecretRoute path='/private' component={Private} />
</div>
</Router>
);
}
}
export default App;
When you click on the Private
link, you are redirected back to /login
route. Great! Let's take it a step further by trying to actually log in and log out. Modify the login component like so:
App.js
...
class Login extends React.Component {
state = {
redirectToPreviousRoute: false
};
login = () => {
AuthService.authenticate(() => {
this.setState({ redirectToPreviousRoute: true });
});
};
render() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToPreviousRoute } = this.state;
if (redirectToPreviousRoute) {
return <Redirect to={from} />;
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
We have modified the Login Component to be able to have a login
function and also redirect back to the route that the user was trying to log onto when the user was denied access. This should be typical behavior of your routing system else users will always be redirected to a particular page rather than where they came from!
Now, we'll have to modify the props of the <Redirect />
component in <SecretRoute />
.
App.js
const SecretRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
AuthService.isAuthenticated === true
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
);
We are almost done. However, wouldn't it be nice if we can provide a logout button for the user after successful authentication? Let's create an <AuthStatus />
component.
App.js
...
const AuthStatus = withRouter(({ history }) => (
AuthService.isAuthenticated ? (
<p>
Welcome! <button onClick={() => {
AuthService.logout(() => history.push('/'))
}}>Sign out</button>
</p>
) : (
<p>You are not logged in.</p>
)
));
In the above code sample, we used withRouter
and history.push
. withRouter
is a higher order component from React Router that allows re-rendering of its component every time the route changes with the same props. history.push
is one way of redirecting asides using the <Redirect />
component from React Router.
Now, go ahead and render the <AuthStatus />
component.
App.js
class App extends Component {
render() {
return (
<Router>
<div style={{width: 1000, margin: '0 auto'}}>
<AuthStatus />
<ul>
<li><Link to='/public'> Public </Link></li>
<li><Link to='/private'> Private </Link></li>
</ul>
<hr/>
<Route path='/public' component={Public} />
<Route path="/login" component={Login}/>
<SecretRoute path='/private' component={Private} />
</div>
</Router>
);
}
}
Now, try it in the browser again. You should be able to log in and log out successfully!
Link Component Customization
Link Component Customization? What's that? It's simple. You'll learn how to customize your links to have a distinctive look when a particular link is active. React Router 4 has an easy way of accomplishing this task.
Have the code below in your App.js
like so:
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
const Home = () => (
<div>
<h2>Home Page</h2>
</div>
)
const Contact = () => (
<div>
<h2>Contact Page</h2>
</div>
)
class App extends React.Component {
render() {
return (
<Router>
<div>
<CustomLink exact={true} to="/">
Home
</CustomLink>
<CustomLink to="/contact">
Contact
</CustomLink>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/contact" component={Contact}/>
</div>
</Router>
)
}
}
export default App;
The <CustomLink />
is in charge of making the active link distinct. Now, what makes up the<CustomLink />
component? Check out the code below:
const CustomLink = ({ children, to, exact }) => (
<Route path={to} exact={exact} children={({ match }) => (
<div className={match ? 'active' : ''}>
{match ? '> ' : ''}
<Link to={to}>
{children}
</Link>
</div>
)}/>
);
It's not complex. The <CustomLink>
harnessed the power of <Route>
. In the code above, it uses the match
object to determine whether to add >
symbol whenever the path matches the URL location.
There are 3 ways to render something with a <Route>
; <Route component>
, <Route render>
, and <Route children>
. The code above used the children
prop. This render prop takes in a function that receives all the same route props as the component
and render
methods, except when a route doesn't match the URL location. This process gives you the power to dynamically adjust your UI based on whether or not the route matches. And that's all we need to create a custom Link!
Handling Non-existent Routes
As a developer, you need to handle scenarios where certain routes don't exist. If a user stumbles upon your site and visits a non-existent route such as /babalawo
. What do you do? Do you just allow your site to break?
This is how to handle this scenario. Add code to your App.js
like so:
App.js
import React, { Component } from 'react';
import {
Route,
Link,
Redirect,
Switch,
BrowserRouter as Router,
} from 'react-router-dom';
const Home = () => (
<div>
<h2>Home Page</h2>
</div>
)
const Contact = () => (
<div>
<h2>Contact Page</h2>
</div>
)
class App extends Component {
render() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/contact" component={Contact}/>
<Route render={() => (<div> Sorry, this page does not exist. </div>)} />
</Switch>
</div>
</Router>
);
}
}
export default App;
In the code above, we imported a new component, <Switch />
from React Router. And we wrapped our routes inside the <Switch />
component. Now, if none of the URLs visited matches the routes defined with a path, then the <Switch />
component invokes the <Route />
with no path and a render function.
Try it out in your browser. Visit a URL that doesn't exist. Your app will display a Sorry, this page does not exist
message.
SideBar Rendering
Sidebars in apps have been in existence for a very long time. Let's learn how to make a sidebar using React Router 4. The first step is to throw our routes into an array like so:
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
} from 'react-router-dom'
const routes = [
{ path: '/',
exact: true,
leftbar: () => <div>Home</div>,
main: () => <h2>Home</h2>
},
{ path: '/about',
leftbar: () => <div>About</div>,
main: () => <h2>About</h2>
},
{ path: '/contact',
leftbar: () => <div>Contact</div>,
main: () => <h2>Contact</h2>
}
]
class App extends React.Component {
render() {
return (
<Router>
<div style={{ display: 'flex' }}>
<div style={{
padding: '10px',
width: '40%',
background: '#FF6347'
}}>
<ul style={{ listStyleType: 'none', padding: 0 }}>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</div>
</div>
</Router>
)
}
}
export default App
In the code above, we have a leftbar
and a main
key. They'll come in handy soon and make our work super easy.
Now, all we need to do is map over the routes array as shown in the code below:
App.js
render() {
return (
<Router>
<div style={{ display: 'flex' }}>
<div style={{
padding: '10px',
width: '40%',
background: '#FF6347'
}}>
<ul style={{ listStyleType: 'none' }}>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
exact={route.exact}
component={route.leftbar}
/>
))}
</div>
<div style={{ flex: 1, padding: '20px' }}>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
exact={route.exact}
component={route.main}
/>
))}
</div>
</div>
</Router>
)
}
In the code above, whenever the route's path matches the URL location, the leftbar component will be rendered. Try it out in your browser and see your left sidebar in action!
Aside: Authenticate a React App with Auth0
We can protect our applications and APIs so that only authenticated users can access them. Let's explore how to do this with a React application using Auth0.
We'll need an Auth0 account to manage authentication. To sign up for a free account, we can follow this link. Next, let's set up an Auth0 application and API so Auth0 can interface with a React App.
Setting Up an Auth0 Application
- Let's go to our Auth0 Dashboard and click the "create a new application" button.
- Let's call our app as "React Demo" and select "Single Page Web Applications".
- In the Settings for our new Auth0 application, let's add
http://localhost:3000/callback
to the Allowed Callback URLs. - If desired, we can set up some social connections. We can then enable them for our app in the Application options under the Connections tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter. For production, make sure to set up the correct social keys and do not leave social connections set to use Auth0 dev keys.
Set Up an API
- Go to APIs in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the Identifier to your API endpoint URL. In this example, this is
http://localhost:3001/api/
. The Signing Algorithm should beRS256
. - You can consult the Node.js example under the Quick Start tab in your new API's settings. We'll implement our Node API in this fashion, using Express, express-jwt, and jwks-rsa.
We're now ready to implement Auth0 authentication on both our React client and Node backend API.
Dependencies and Setup
There are only two dependencies that we really need to install: auth0.js
and history
. To do that, let's issue npm install --save auth0-js history
in the project root.
Note: As we want the best security available, we are going to rely on the Auth0 login page. This method consists of redirecting users to a login page hosted by Auth0 that is easily customizable right from the Dashboard.
After installing it, we can create an authentication service to interface with the auth0.js
script. Let's call this service Auth
and create it in the src/Auth/
directory with the following code:
import history from '../history';
import auth0 from 'auth0-js';
export default class Auth {
auth0 = new auth0.WebAuth({
// the following three lines MUST be updated
domain: 'bkrebs.auth0.com',
audience: 'https://bkrebs.auth0.com/userinfo',
clientID: '3co4Cdt3h3x8En7Cj0s7Zg5FxhKOjeeK',
redirectUri: 'http://localhost:3000/callback',
responseType: 'token',
scope: 'openid'
});
constructor() {
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.handleAuthentication = this.handleAuthentication.bind(this);
this.isAuthenticated = this.isAuthenticated.bind(this);
}
handleAuthentication() {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken) {
this.setSession(authResult);
history.replace('/home');
} else if (err) {
history.replace('/home');
console.log(err);
}
});
}
setSession(authResult) {
// Set the time that the access token will expire at
let expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
localStorage.setItem('access_token', authResult.accessToken);
localStorage.setItem('expires_at', expiresAt);
// navigate to the home route
history.replace('/home');
}
login() {
this.auth0.authorize();
}
logout() {
// Clear access token and expiration from local storage
localStorage.removeItem('access_token');
localStorage.removeItem('expires_at');
// navigate to the home route
history.replace('/home');
}
isAuthenticated() {
// Check whether the current time is past the
// access token's expiry time
let expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return new Date().getTime() < expiresAt;
}
}
The Auth
service just created contains functions to deal with various steps of the sign in/sign up process. The following list briefly summarizes these functions and their descriptions:
handleAuthentication
: looks for the result of the authentication in the URL hash. Then, process the result with theparseHash
method fromauth0-js
;setSession
: sets the user's access token and the access token's expiry time;login
: initiates the login process, redirecting users to the login page;logout
: removes the user's tokens and expiry time from browser storage;isAuthenticated
: checks whether the expiry time for the user's access token has passed;
Besides these functions, the class contains a field called auth0
that is initialized with values extracted from the Auth0 application. Let's keep in mind that we need to update them accordingly before proceeding.
Attentive readers probably noticed that the Auth
service also imports a module called history
that we haven't talked about. We can define this module in only two lines, but let's define it in a file to provide reusability. Let's call this file ./src/history/history.js
and add the following code:
import createHistory from 'history/createBrowserHistory'
export default createHistory()
After creating both elements, we can refactor our App
component to make use of the Auth
service.
import React, { Component } from 'react';
import { Navbar, Button } from 'react-bootstrap';
import './App.css';
class App extends Component {
goTo(route) {
this.props.history.replace(`/${route}`)
}
login() {
this.props.auth.login();
}
logout() {
this.props.auth.logout();
}
render() {
const { isAuthenticated } = this.props.auth;
// ... render the view
}
}
export default App;
Note that we are passing this service through props
. Therefore, when including the App
component, we need to inject Auth
into it: <App auth={auth} />
.
Considering that we are using the Auth0 login page, our users are taken away from the application. However, after they authenticate, users automatically return to the callback URL that we set up previously (http://localhost:3000/callback
). This means that we need to create a component responsible for this URL:
import React, { Component } from 'react';
import loading from './loading.svg';
class Callback extends Component {
render() {
const style = //...
return (
<div style={style}>
<img src={loading} alt="loading"/>
</div>
);
}
}
export default Callback;
This component can just contain a loading indicator that keeps spinning while the application sets up a client-side session for the users. After the session is set up, we can redirect users to another route.
Please refer to the official Quick Start Guide to see, step by step, how to properly secure a React application. Besides the steps shown in this section, the guide also shows:
- How to manage profile information of authenticated users
- How to properly call an API
- How to control which routes users can see/interact with
- How to deal with expiry time of users' access token
Conclusion
Understanding React Router 4 requires a shift in your mental model of routing. I covered the main API concepts for using React Router 4 for the web in this tutorial. However, you can always consult the official documentation for more information.
"Understanding React Router 4 requires a shift in your mental model of routing."
TWEET THIS
![]()