React logo

How to handle routing and navigation in React JS

Giovanni Antonaccio
6 min readSep 19, 2019

In the beginning of a React application there are two steps that should be followed to increase productivity in my opinion:

  1. Configure the app basis with tools like ESLint, Prettier, EditorConfig, Jest and Reactotron.
  2. Configure routing and navigation in order to control the routes that the user will have access.

In this post I will show you how I handle the second item in a way that leaves our app very well organized!

I’m assuming you already have a react application configured. If not, just follow the steps here: https://github.com/facebook/create-react-app

Installation

First step is to install react-router-dom lib, that will be responsible to handle it for us:

yarn add react-router-dom

Once installed, we will create the folder ./src/routes to add our routes configuration. Inside this folder we will have two files:

  • index.js → Responsible to return a page corresponding to the route informed by the user. It will work like a router for our app.
  • Route.js → This component will be responsible for redirecting the user based on his permissions. We will use it to redirect the user to login page case he tries to access a route that needs authentication for example.

Configuring the routes

Let’s start with index.js. Inside this file we will import two components from react-router-dom:

  • Switch
  • Route

Later we will replace this Route with our own component, but for now we don’t have to worry about it.

We will import all our pages (which come from ./src/pages/COMPONENT_NAME/index.js). They will be returned within each route, where we will link to the desired paths for each of them.

Also note that we are passing a property called isPrivate, which for now will have no influence, but later will help us to redirect the user based on his permissions within the application.

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import SignIn from '../pages/SignIn';
import SignUp from '../pages/SignUp';
import Dashboard from '../pages/Dashboard';
export default function Routes() {
return (
<Switch>
<Route path="/" exact component={SignIn} />
<Route path="/register" component={SignUp} />
<Route path="/dashboard" component={Dashboard} isPrivate /> {/* redirect user to SignIn page if route does not exist and user is not authenticated */}
<Route component={SignIn} />
</Switch>
);
}

Switch checks the paths in the order they are disposed inside the component and always returns the first route that contains the path value as a substring. Since we have a path with “/”and other with “/register”, the second would never be called because the first would always be selected. To solve this problem we need to add the exact property, so it will just return the SignIn component if the route is exactly "/".

Also note that we need to import React inside this file because we are using JSX.

Configuring history

We want to be able to redirect our user inside our app automatically based on some events, like returning him to Sign In page if he press logout button for example. To handle this we will use a library called history.

First step is to install it:

yarn add history

Now we will create the file ./src/services/history.js:

import { createBrowserHistory } from 'history';const history = createBrowserHistory();export default history;

Configuring the router

Now we need to configure our router, that we be inside ./src/App.js. We will import three files:

  • Router → This component will do the redirections.
  • history → Will be responsible for allowing us to make redirections in our app without the need for the user to type a path in browser.
  • Routes → Our routes file will return one of the pages depending on the path we inform.

Now we just need to place our router around our Routes component and we will have our app navigation working!

import React from 'react';
import { Router } from 'react-router-dom';
import history from './services/history';
import Routes from './routes';
function App() {
return (
<Router history={history}>
<Routes />
</Router>
);
}
export default App;

Is very usual that we have routes in our applications that we don’t want to be accessible until the user Sign In, so we can retrieve his informations. What we have done so far let the user to access any route he wants since he types the correct path.

Now it’s the moment to create our own Route component, that we mentioned in the beginning of this article.

Inside this file we will have a functional component called RouteWrapper (the name will not be Route because we will use Route and Redirect from react-router-dom.

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export default function RouteWrapper() {}

Next step is to import a few properties to our new component:

  • component → Here we will receive the page we want to render, sent by our file index.
  • isPrivate → This property will be used to indicate if the route can be accessed without authentication.
  • …rest → Here we will use the spread operator to pass all other parameters that our component receives to rest variable.
import React from ‘react’; 
import { Route, Redirect } from ‘react-router-dom’;
export default function RouteWrapper({
component: Component,
isPrivate,
…rest
}) {}

Now that we imported all necessary props, we need to add the redirection logic. We will have a variable called signed that will indicate if the user is signed or not to the system (this variable will have a constant value for now, but later redux can be used to store this state).

The business logic will be:

  1. If the route is private and the user is not signed in, then we will send him back to sign in page.
  2. If the route is not private but the user is already signed in, then we send him to the main page, that in our case will be the dashboard page.
  3. If he does not attend any of the conditions above then we return a ROute from react-router-dom passing the component and all other parameters.
import React from 'react'; 
import { Route, Redirect } from 'react-router-dom';
export default function RouteWrapper({
component: Component,
isPrivate,
...rest
}) {
const signed = true;

/**
* Redirect user to SignIn page if he tries to access a private route
* without authentication.
*/
if (isPrivate && !signed) {
return <Redirect to="/" />;
}
/**
* Redirect user to Main page if he tries to access a non private route
* (SignIn or SignUp) after being authenticated.
*/
if (!isPrivate && signed) {
return <Redirect to="/dashboard" />;
}

/**
* If not included on both previous cases, redirect user to the desired route.
*/
return <Route {...rest} component={Component} />;
}

To finish our component we need to add PropTypes validation. The first step is to install the library:

yarn add prop-types

After this we just need to import and make the necessary validations. Our component will be like this:

import React from 'react';
import PropTypes from 'prop-types';
import { Route, Redirect } from 'react-router-dom';
export default function RouteWrapper({
component: Component,
isPrivate,
...rest
}) {
const signed = true;
/**
* Redirect user to SignIn page if he tries to access a private route
* without authentication.
*/
if (isPrivate && !signed) {
return <Redirect to="/" />;
}
/**
* Redirect user to Main page if he tries to access a non private route
* (SignIn or SignUp) after being authenticated.
*/
if (!isPrivate && signed) {
return <Redirect to="/dashboard" />;
}
/**
* If not included on both previous cases, redirect user to the desired route.
*/
return <Route {...rest} component={Component} />;
}
RouteWrapper.propTypes = {
isPrivate: PropTypes.bool,
component: PropTypes.oneOfType([PropTypes.element, PropTypes.func])
.isRequired,
};
RouteWrapper.defaultProps = {
isPrivate: false,
};

The last step is to change the Route component that we used inside ./src/routes/index.js in order for it to use our component instead of the one from react-router-dom:

import React from 'react';
// import { Switch, Route } from 'react-router-dom';
import { Switch } from 'react-router-dom';
import Route from './Route';
// rest of the code ommitted.

Thats it! If you follow all these steps you should have very modular code that can be extended easily if you keep using the same pattern.

Please note that authentication is still manual and we will cover the automation of this part in a future post.

You can check the full implementation on codesandbox!

I have another post where I continue this app adding reusable layouts to it. If you want to add this to your project check this link.

I hope this tutorial can help you with your projects! Thanks for reading!

--

--

Giovanni Antonaccio

Full Stack developer passionate about NodeJS, React JS and React Native.