Tutorial

How To Set Up Conditional and Responsive Routing with React Router v4

JavaScriptReact

Introduction

Responsive routing in React involves serving different routes to users based on the viewport of their device. CSS media queries are usually used to achieve this, but this restricts you to either showing or not showing different elements by using the CSS props. With responsive routes, you can now serve entire separate views of your React applications to different users based directly on their screen sizes.

In this tutorial, we will show you how to implement routing and serving responsive routes in your React applications. By following this tutorial, you will build a user dashboard application that serves different routes to users based on the size of their device screens.

Demo showing the cards adjusting to the size of the page

Prerequisites

To complete this tutorial, you will need:

This tutorial was verified with Node v14.2.0, npm v6.14.5, react v16.3.2, react-router-dom v5.2.0, and react-media v1.10.0.

Step 1 — Setting Up the Project

To start your project, use npx and create-react-app to create a new React application:

  • npx create-react-app responsive-routing

Then, navigate to the new project directory:

  • cd responsive-routing

Next, install the necessary modules you will need to successfully build this demo. These modules are the react-router-dom and react-media. You can install these by running the command:

  • npm install react-router-dom@5.2.0 react-media@1.10.0

Now, you can start the application by running the command:

  • npm start

Note: While not required for routing, this tutorial uses the Bulma CSS framework for styling and layout.

You can add Bulma with the following terminal command:

  • npm install bulma@0.6.2

And by adding the following to your index.js:

index.js
import 'bulma/css/bulma.css';

In this step you’ve set up your project and added the Bulma framework for styling and layout.

Step 2 — Adding React Router

To add routing to your project, you will need to modify your index.js file to render the router at the root of your element hierarchy:

  • nano index.js

First, import BrowserRouter from react-router-dom and alias it to Router:

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from "react-router-dom";
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

Then, replace <React> with <Router>:

index.js
ReactDOM.render(
  <Router>
    <App />
  </Router>,
  document.getElementById('root')
);

Your application is now set up to use React Router.

Step 3 — Creating the Nav Component

The GitHub logo in the center of the page will serve as the navigation part of your application.

In your src directory, create a new directory called Nav:

  • mkdir src/Nav

You will need to add the GitHub logo and save it as logo.svg in this directory.

Next, create an index.js file in this directory:

  • nano src/Nav/index.js

And add the following code:

src/Nav/index.js
import React from 'react';
import './Nav.css';
import logo from './logo.svg';

const Nav = () => (
  <nav>
    <img src={logo} alt="Logo" />
  </nav>
);

export default Nav;

Next, create a Nav.css file in this directory:

  • nano src/Nav/Nav.css

The navigation component has the following styling:

src/Nav/Nav.css
nav {
  display: flex;
  justify-content: center;
  height: 50px;
  margin-bottom: 10px;
}

nav > img {
  display: block;
  width: 50px;
  height: auto;
}

Now, let’s render the Nav component by modifying the App.js file.

  • nano src/App.js

Import the Nav component and use it in your App component:

src/App.js
import React, { Component } from 'react';
import Nav from './Nav';

class App extends Component {
  render() {
    return (
      <div>
        <Nav />
      </div>
    );
  }
}

export default App;

Now, when you open your app in a web browser, you should see the logo you added.

Step 4 — Creating the UsersCard Component

The user cards will be responsible for displaying details of the user. It will contain information like an avatar, name, and username. It will also display followers, following, and repos.

In the src directory of your app, create a new Users directory:

  • mkdir src/Users

Next, create a UsersCard.js file in this directory:

  • nano src/Users/UsersCard.js

And add the following code:

src/Users/UsersCard.js
import React from 'react';
import { Link } from 'react-router-dom';
import './UsersCard.css'

const UsersCard = ({ user, match }) => <Link to={`${match.url}/${user.id}`} className="column card">
  <img src={user.avatar} alt=""/>
  <p className="users-card__name">{user.name}</p>
  <p className="users-card__username">@{user.username}</p>
  <div className="users-card__divider"></div>
  <div className="users-card__stats">
    <div>
      <p>{user.followers}</p>
      <span>Followers</span>
    </div>
    <div>
      <p>{user.following}</p>
      <span>Following</span>
    </div>
    <div>
      <p>{user.repos}</p>
      <span>Repositories</span>
    </div>
  </div>
</Link>;

export default UsersCard;

The Link component from react-router-dom is used to allow the user to navigate to view details of a single user when the card is clicked.

For example, if a UsersCard has an id of 10009, the Link component will generate a URL like this:

localhost:3000/10009
  • localhost:3000 represents the current URL.
  • 10009 represents the $user.id.

All this information will be passed when the component is rendered.

Next, create a UsersCard.css file in this directory:

  • nano src/users/UsersCard.css

The UsersCard component has the following styling:

src/Users/UsersCard.css
.card {
  border-radius: 2px;
  background-color: #ffffff;
  box-shadow: 0 1.5px 3px 0 rgba(0, 0, 0, 0.05);
  max-width: 228px;
  margin: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0;
}

.card img {
  width: 50px;
  height: auto;
  border-radius: 50%;
  display: block;
  padding: 15px 0;
}

.users-card__name {
  font-weight: 400;
  font-size: 16.5px;
  line-height: 1.19;
  letter-spacing: normal;
  text-align: left;
  color: #25292e;
}

.users-card__username {
  font-size: 14px;
  color: #707070;
}

.users-card__divider {
  border: solid 0.5px #efefef;
  width: 100%;
  margin: 15px 0;
}

.users-card__stats {
  display: flex;
}

.users-card__stats p {
  font-size: 20px;
}

.users-card__stats div {
  margin: 10px;
  text-align: center;
}

.users-card__stats span {
  color: #707070;
  font-size: 12px;
}

At this point you have a UsersCard component. Next, you will need to display theses cards in a list.

Step 5 — Creating the UsersList Component

To get your application to list users, you will need to first create a UsersList component.

Create a UsersCard.js file in the src/Users directory:

  • nano UsersList.js

Let’s edit the UsersList.js as follows.

First, you will make the necessary imports:

src/Users/UsersList.js
import React from 'react';
import UsersCard from './UsersCard';
import './UsersList.css';

Define a listOfUsersPerRow function that will build out a UsersCard coinciding with their position in the array of users:

// ...

const listOfUsersPerRow = (users, row, itemsPerRow, match) =>
  users
    .slice((row - 1) * itemsPerRow, row * itemsPerRow)
    .map(user => <UsersCard user={user} key={user.id} match={match} />);

Define a listOfRows function that will build out "columns" containing UsersCards defined by the amount of itemsPerRow:

// ...

const listOfRows = (users, itemsPerRow, match) => {
  const numberOfUsers = users.length;
  const rows = Math.ceil(numberOfUsers / itemsPerRow);

  return Array(rows)
    .fill()
    .map((val, rowIndex) => (
    <div className="columns">
        {listOfUsersPerRow(users, rowIndex + 1, itemsPerRow, match)}
    </div>
  ));
};

The listOfUsersPerRow and listOfRows functions ensure that you have no more than the specified number of cards on each row.

Then, use the functions to create a UsersList:

src/Users/UsersList.js
//...

const UsersList = ({ users, itemsPerRow = 2, match }) => (
  <div className="cards">
    <h3 className="is-size-3 has-text-centered">Users</h3>
    {listOfRows(users, itemsPerRow, match)}
  </div>
);

export default UsersList;

Next, create a UsersList.css file in this directory:

  • nano src/Users/UsersList.css

The UsersList component has the following styling:

src/Users/UsersList.css
.cards {
  margin-left: 20px;
}

.columns {
  margin-top: 0;
}

At this point, you have a UsersList component consisting of UsersCards. Next, you will need a detailed view for an individual user.

Step 6 — Creating the UsersDetails Component

When a single UsersCard is clicked from the UsersList, the single UsersCard is displayed under a details section.

Create a UsersDetails.js file in the src/Users directory:

  • nano UsersDetails.js

And add the following code:

src/Users/UsersDetails.js
import React from 'react';
import UsersCard from './UsersCard';

const UsersDetails = ({ user, match }) => <div>
  <h3 className="is-size-3 has-text-centered">Details</h3>
    <UsersCard user={user} match={match} />
  </div>;

export default UsersDetails;

At this point, you have a UsersDetails component. Next, you will display UsersLists and UsersDetails.

Step 7 — Creating the UsersDashboard Component

To make the dashboard component, you will display the UsersList and, when a UsersCard is clicked, display the UsersDetails on the side of the screen without having to reload the page.

Create a UsersDashboard.js file in the src/Users directory:

  • nano src/Users/UsersDashboard.js

And add the following code:

src/Users/UsersDashboard.js
import React from 'react';
import { Route } from 'react-router-dom';
import UsersList from './UsersList';
import UsersDetails from './UsersDetails';

const UsersDashboard = ({ users, user, match }) => (
  <div className="columns">
    <div className="column">
      <UsersList users={users} match={match} />
    </div>
    <div className="column">
      <Route
        path={match.url + '/:id'}
        render={props => (
          <UsersDetails
            user={
              users.filter(
                user => user.id === parseInt(props.match.params.id, 10)
              )[0]
            }
            match={match}
          />
        )}
      />
    </div>
  </div>
);

export default UsersDashboard;

In this snippet, you used the Route component provided by react-router-dom as a component to display the specific user detail when the card is clicked.

At this point, you have all the components for your application.

Step 8 — Putting it All Together

Now, let’s put this all together.

Revisit the App.js file:

  • nano src/App.js

Add Redirect and UsersDashboard:

src/App.js
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import Nav from './Nav';
import UsersDashboard from './Users/UsersDashboard';
import './App.css';

Add state containing an array of users:

src/App.js
//...

class App extends Component {
  state = {
    users: [
      {
        id: 39191,
        avatar: 'https://avatars0.githubusercontent.com/u/39191?v=4',
        name: 'Paul Irish',
        username: 'paulirish',
        followers: '12k',
        following: '1k',
        repos: '1.5k'
      },
      // ... other user data
    ]
  };

  // ...
}

// ...

Add Route and UsersDashboard to your App component:

class App extends Component {
  // ...

  render() {
    return (
      <div className="App">
        <Nav />
        <Route
          path="/dashboard"
          render={props => (
            <UsersDashboard users={this.state.users} {...props} />
          )}
        />
        <Redirect from="/" to="/dashboard"/>
        <Redirect from="/users" to="/dashboard"/>
      </div>
    );
  }
}

// ...

Now, when viewing your application in a web browser, you should see a UsersList. When clicking on a UsersCard, you will see it display in the UsersDetails.

Step 9 — Setting Up Responsive Routing

When users visit this application, no matter the screen size, they get this same view and functionality. In full-blown applications, it’s good to give the users experiences they can enjoy properly. One way to do that is to serve them views that match their exact device sizes. You are now going to implement this in your application.

When visiting the application on a wide screen, the user is redirected to the /dashboard route of the application, and when viewing on a smaller screen, the user will be directed to the /users route of the application.

Update the src/App.js file to look like this:

src/App.js
import React, { Component } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom'; // add Switch
import Media from 'react-media'; // add Media
import Nav from './Nav';
import UsersList from './Users/UsersList'; // add UsersList
import UsersDetails from './Users/UsersDetails'; // add UsersDetails
import UsersDashboard from './Users/UsersDashboard';
import './App.css';

class App extends Component {
  // ...

  render() {
    return (
      <div className="App">
        <Nav />
        <Media query="(max-width: 599px)">
          {matches =>
            matches ? (
              <Switch>
                <Route
                  exact
                  path="/users"
                  render={props => (
                    <UsersList users={this.state.users} {...props} />
                  )}
                />
                <Route
                  path="/users/:id"
                  render={props => (
                    <UsersDetails
                      user={
                        this.state.users.filter(
                          user =>
                            user.id === parseInt(props.match.params.id, 10)
                        )[0]
                      }
                      {...props}
                    />
                  )}
                />
                <Redirect from="/" to="/users"/>
                <Redirect from="/dashboard" to="/users"/>
              </Switch>
            ) : (
              <Switch>
                <Route
                  path="/dashboard"
                  render={props => (
                    <UsersDashboard users={this.state.users} {...props} />
                  )}
                />
                <Redirect from="/" to="/dashboard"/>
                <Redirect from="/users" to="/dashboard"/>
              </Switch>
            )
          }
        </Media>
      </div>
    );
  }
}

export default App;

In this snippet, you used the Media component to check the size of the screen. If the screen width is less than 599px, you set what you wanted to be displayed for different routes and also redirect the / and /dashboard routes to the /users route.

If the screen size is greater than 599px, you display the full user dashboard as established in the previous step.

Run the application:

npm start

Interact with your application and adjust your screen size to see how routes are handled differently when interacting with the application.

Serving different routes based on screen sizes offers something beyond media queries, because you can now serve specially designed components to users based on their device sizes.

Conclusion

In this article, you were introduced to component-based routing with React and how to implement conditional rendering in your React applications.

For a complete code sample of this tutorial, check out the responsive-routing repository on GitHub.

If you’d like to learn more about React, take a look at our How To Code in React.js series, or check out our React topic page for exercises and programming projects.

Creative Commons License