14 min read

Routing is essential to most web applications. You cannot cover all of the features of your application in just one page. It would be overloaded, and your user would find it difficult to understand. Sharing links to pictures, profiles, or posts is also very important for a social network such as Graphbook. It is also crucial to split content into different pages, due to search engine optimization (SEO).

This article is taken from the book Hands-on Full-Stack Web Development with GraphQL and React by Sebastian Grebe. This book will guide you in implementing applications by using React, Apollo, Node.js, and SQL. By the end of the book, you will be proficient in using GraphQL and React for your full-stack development requirements. To follow along with the examples implemented in this article, you can download the code from the book’s GitHub repository.

In this article, we will learn how to do client-side routing in a React application. We will cover the installation of React Router, implement routes, create user profiles with GraphQL backend, and handle manual navigation.

Installing React Router

We will first start by installing and configuring React Router 4 by running npm:

npm install --save react-router-dom

From the package name, you might assume that this is not the main package for React. The reason for this is that React Router is a multi-package library. That comes in handy when using the same tool for multiple platforms. The core package is called react-router.

There are two further packages. The first one is the react-router-dom package, which we installed in the preceding code, and the second one is the react-router-native package. If at some point, you plan to build a React Native app, you can use the same routing, instead of using the browser’s DOM for a real mobile app.

The first step that we will take introduces a simple router to get our current application working, including different paths for all of the screens. There is one thing that we have to prepare before continuing. For development, we are using the webpack development server. To get the routing working out of the box, we will add two parameters to the webpack.client.config.js file. The devServer field should look as follows:

devServer: {
  port: 3000,
  open: true,
  historyApiFallback: true,
},

The historyApiFallback field tells the devServer to serve the index.html file, not only for the root path, http://localhost:3000/ but also when it would typically receive a 404 error. That happens when the path does not match a file or folder that is normal when implementing routing.

The output field at the top of the config file must have a publicPath property, as follows:

output: {
  path: path.join(__dirname, buildDirectory),
  filename: 'bundle.js',
  publicPath: '/',
},

The publicPath property tells webpack to prefix the bundle URL to an absolute path, instead of a relative path. When this property is not included, the browser cannot download the bundle when visiting the sub-directories of our application, as we are implementing client-side routing.

Implementing your first route

Before implementing the routing, we will clean up the App.js file. Create a Main.js file next to the App.js file in the client folder. Insert the following code:

import React, { Component } from 'react';
import Feed from './Feed';
import Chats from './Chats';
import Bar from './components/bar';
import CurrentUserQuery from './components/queries/currentUser';
export default class Main extends Component {  render() {    return (      <CurrentUserQuery>        <Bar changeLoginState={this.props.changeLoginState}/>        <Feed />        <Chats />      </CurrentUserQuery>    );  }}

As you might have noticed, the preceding code is pretty much the same as the logged in condition inside the App.js file. The only change is that the changeLoginState function is taken from the properties, and is not directly a method of the component itself. That is because we split this part out of the App.js and put it into a separate file. This improves reusability for other components that we are going to implement.

Now, open and replace the render method of the App component to reflect those changes, as follows:

render() {
  return (
    <div>
      <Helmet>
        <title>Graphbook - Feed</title>
        <meta name="description" content="Newsfeed of all your friends   
         on Graphbook" />
      </Helmet>
      <Router loggedIn={this.state.loggedIn} changeLoginState=
       {this.changeLoginState}/>
    </div>
  )
}

If you compare the preceding method with the old one, you can see that we have inserted a Router component, instead of directly rendering either the posts feed or the login form. The original components of the App.js file are now in the previously created Main.js file. Here, we pass the loggedIn state variable and the changeLoginState function to the Router component. Remove the dependencies at the top, such as the Chats and Feed components, because we won’t use them any more thanks to the new Main component. Add the following line to the dependencies of our App.js file:

import Router from './router';

To get this code working, we have to implement our custom Router component first. Generally, it is easy to get the routing running with React Router, and you are not required to separate the routing functionality into a separate file, but, that makes it more readable. To do this, create a new router.js file in the client folder, next to the App.js file, with the following content:

import React, { Component } from 'react';
import LoginRegisterForm from './components/loginregister';
import Main from './Main';
import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom';
export default class Routing extends Component {  render() {    return (      <Router>        <Switch>          <Route path="/app" component={() => <Main changeLoginState=          {this.props.changeLoginState}/>}/>        </Switch>      </Router>    )  }}

At the top, we import all of the dependencies. They include the new Main component and the react-router package. The problem with the preceding code is that we are only listening for one route, which is /app. If you are not logged in, there will be many errors that are not covered. The best thing to do would be to redirect the user to the root path, where they can log in.

Advanced routing with React Router

The primary goal of this article is to build a profile page, similar to Facebook, for your users. We need a separate page to show all of the content that a single user has entered or created.

Parameters in routes

We have prepared most of the work required to add a new user route. Open up the router.js file again. Add the new route, as follows:

<PrivateRoute path="/user/:username" component={props => <User {...props} changeLoginState={this.props.changeLoginState}/>} loggedIn={this.props.loggedIn}/>

Those are all of the changes that we need to accept parameterized paths in React Router. We read out the value inside of the new user page component. Before implementing it, we import the dependency at the top of router.js to get the preceding route working:

import User from './User';

Create the preceding User.js file next to the Main.js file. Like the Main component, we are collecting all of the components that we render on this page. You should stay with this layout, as you can directly see which main parts each page consists of. The User.js file should look as follows:

import React, { Component } from 'react';
import UserProfile from './components/user';
import Chats from './Chats';
import Bar from './components/bar';
import CurrentUserQuery from './components/queries/currentUser';
export default class User extends Component {  render() {    return (      <CurrentUserQuery>        <Bar changeLoginState={this.props.changeLoginState}/>        <UserProfile username={this.props.match.params.username}/>        <Chats />      </CurrentUserQuery>    );  }}

We use the CurrentUserQuery component as a wrapper for the Bar component and the Chats component. If a user visits the profile of a friend, they see the common application bar at the top. They can access their chats on the right-hand side, like in Facebook.

We removed the Feed component and replaced it with a new UserProfile component. Importantly, the UserProfile receives the username property. Its value is taken from the properties of the User component. These properties were passed over by React Router. If you have a parameter, such as a username, in the routing path, the value is stored in the match.params.username property of the child component. The match object generally contains all matching information of React Router.

From this point on, you can implement any custom logic that you want with this value. We will now continue with implementing the profile page.

Follow these steps to build the user’s profile page:

  1. Create a new folder, called user, inside the components folder.
  2. Create a new file, called index.js, inside the user folder.
  3. Import the dependencies at the top of the file, as follows:
import React, { Component } from 'react';
import PostsQuery from '../queries/postsFeed';
import FeedList from '../post/feedlist';
import UserHeader from './header';
import UserQuery from '../queries/userQuery';

The first three lines should look familiar. The last two imported files, however, do not exist at the moment, but we are going to change that shortly. The first new file is UserHeader, which takes care of rendering the avatar image, the name, and information about the user. Logically, we request the data that we will display in this header through a new Apollo query, called UserQuery.

  1. Insert the code for the UserProfile component that we are building at the moment beneath the dependencies, as follows:
export default class UserProfile extends Component {
  render() {
    const query_variables = { page: 0, limit: 10, username: 
    this.props.username };
    return (
      <div className="user">
        <div className="inner">
          <UserQuery variables={{username: this.props.username}}>
            <UserHeader/>
          </UserQuery>
        </div>
        <div className="container">
          <PostsQuery variables={query_variables}>
            <FeedList/>
          </PostsQuery>
        </div>
      </div>
    )
  }
}

The UserProfile class is not complex. We are running two Apollo queries simultaneously. Both have the variables property set. The PostQuery receives the regular pagination fields, page and limit, but also the username, which initially came from React Router. This property is also handed over to the UserQuery, inside of a variables object.

  1. We should now edit and create the Apollo queries, before programming the profile header component. Open the postsFeed.js file from the queries folder.

To use the username as input to the GraphQL query we first have to change the query string from the GET_POSTS variable. Change the first two lines to match the following code:

query postsFeed($page: Int, $limit: Int, $username: String) { 
  postsFeed(page: $page, limit: $limit, username: $username) {

Add a new line to the getVariables method, above the return statement:

if(typeof variables.username !== typeof undefined) {
  query_variables.username = variables.username;
}

If the custom query component receives a username property, it is included in the GraphQL request. It is used to filter posts by the specific user that we are viewing.

  1. Create a new userQuery.js file in the queries folder to create the missing query class.
  2. Import all of the dependencies and parse the new query schema with graphl-tag, as follows:
import React, { Component } from 'react';
import { Query } from 'react-apollo';
import Loading from '../loading';
import Error from '../error';
import gql from 'graphql-tag';
const GET_USER = gql`  query user($username: String!) {     user(username: $username) {       id      email      username      avatar    }  }`;

The preceding query is nearly the same as the currentUser query. We are going to implement the corresponding user query later, in our GraphQL API.

  1. The component itself is as simple as the ones that we created before. Insert the following code:
export default class UserQuery extends Component {
  getVariables() {
    const { variables } = this.props;
    var query_variables = {};
    if(typeof variables.username !== typeof undefined) {
      query_variables.username = variables.username;
    }
    return query_variables;
  }
  render() {
    const { children } = this.props;
    const variables = this.getVariables();
    return(
      <Query query={GET_USER} variables={variables}>
        {({ loading, error, data }) => {
          if (loading) return <Loading />;
          if (error) return <Error><p>{error.message}</p></Error>;
          const { user } = data;
          return React.Children.map(children, function(child){
            return React.cloneElement(child, { user });
          })
        }}
      </Query>
    )
  }
}

We set the query property and the parameters that are collected by the getVariables method to the GraphQL Query component. The rest is the same as any other query component that we have written. All child components receive a new property, called user, which holds all the information about the user, such as their name, their email, and their avatar image.

  1. The last step is to implement the UserProfileHeader component. This component renders the user property, with all its values. It is just simple HTML markup. Copy the following code into the header.js file, in the user folder:
import React, { Component } from 'react';export default class UserProfileHeader extends Component {  render() {    const { avatar, email, username } = this.props.user;    return (      <div className="profileHeader">        <div className="avatar">          <img src={avatar}/>        </div>        <div className="information">          <p>            {username}          </p>          <p>            {email}          </p>          <p>You can provide further information here and build           your really personal header component for your users.</p>        </div>      </div>    )  }}

We have finished the new front end components, but the UserProfile component is still not working. The queries that we are using here either do not accept the username parameter or have not yet been implemented.

Querying the user profile

With the new profile page, we have to update our back end accordingly. Let’s take a look at what needs to be done, as follows:

  1. We have to add the username parameter to the schema of the postsFeed query and adjust the resolver function.
  2. We have to create the schema and the resolver function for the new UserQuery component.

We will begin with the postsFeed query.

Edit the postsFeed query in the RootQuery type of the schema.js file to match the following code:

postsFeed(page: Int, limit: Int, username: String): PostFeed @auth

Here, I have added the username as an optional parameter.

Now, head over to the resolvers.js file, and take a look at the corresponding resolver function. Replace the signature of the function to extract the username from the variables, as follows:

postsFeed(root, { page, limit, username }, context) {

To make use of the new parameter, add the following lines of code above the return statement:

if(typeof username !== typeof undefined) {
  query.include = [{model: User}];
  query.where = { '$User.username$': username };
}

In the preceding code, we fill the include field of the query object with the Sequelize model that we want to join. This allows us to filter the associated Chats model in the next step.

Then, we create a normal where object, in which we write the filter condition. If you want to filter the posts by an associated table of users, you can wrap the model and field names that you want to filter by with dollar signs. In our case, we wrap User.username with dollar signs, which tells Sequelize to query the User model’s table and filter by the value of the username column.

No adjustments are required for the pagination part. The GraphQL query is now ready. The great thing about the small changes that we have made is that we have just one API function that accepts several parameters, either to display posts on a single user profile, or to display a list of posts like a news feed.

Let’s move on and implement the new user query. Add the following line to the RootQuery in your GraphQL schema:

user(username: String!): User @auth

This query only accepts a username, but this time it is a required parameter in the new query. Otherwise, the query would make no sense, since we only use it when visiting a user’s profile through their username. In the resolvers.js file, we will now implement the resolver function using Sequelize:

user(root, { username }, context) {
  return User.findOne({
    where: {
      username: username
    }
  });
},

In the preceding code, we use the findOne method of the User model by Sequelize, and search for exactly one user with the username that we provided in the parameter.

We also want to display the email of the user on the user’s profile page. Add the email as a valid field on the User type in your GraphQL schema with the following line of code:

email: String

With this step, our back end code and the user page are ready.

This article walked you through the installation process of React Router and how to implement a route in React. Then we moved on to more advanced stuff by implementing a user profile, similar to Facebook, with a GraphQL backend.

If you found this post useful, do check out the book, Hands-on Full-Stack Web Development with GraphQL and React. This book teaches you how to build scalable full-stack applications while learning to solve complex problems with GraphQL.

Read Next

How to build a Relay React App [Tutorial]

React vs. Vue: JavaScript framework wars

Working with the Vue-router plugin for SPAs