Getting started with React: APIs, Mock Servers and Routing

Arun Kant Pant
26 min readApr 19, 2020

This blog is part of the series ‘Getting started with React’ .

In the last part, we discussed about lifecycle methods in class based components. So far, we have only talked about APIs and emulated async tasks by using setTimeout( ) or setInterval( ).

In this part, we will create an application where we will mock a login flow and display a page with data about some famous action movie characters, and some famous action movie villains as well.

Let us first start by generating our application base by using create-react-app:

create-react-app heroes

This will create the basic structure of our application — heroes.

Let us convert the App.js file into a class based component (since, as mentioned in the previous parts as well, we will cover hooks in functional components in an entirely different section later). Replace its contents with the following:

import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<h1>Hey there</h1>
</div>
)
}
}
export default App;

On running yarn start you should be able to see your app running in the browser.

Let us create our first Login Component, and place it inside App.js to be rendered.

Create a folder called Containers (to store any stateful react components), and inside it another folder called Login. We will place our Login.js file inside this folder. We will also create a css modules file for our component in the same folder called Login.module.css as shown:

Login.js and Login.module.css files inside the Containers/Login folder

Let us create the view for the Login component. We will create a class based login component as follows:

import React, { Component } from 'react';
import classes from './Login.module.css';
class Login extends Component {
render() {
return (
<div className={classes.loginPage}>
<div className={classes.form}>
<div>
<h2>Action Heroes</h2>
</div>
<div className={classes.formInput}>
<input placeholder="Username" />
</div>
<div className={classes.formInput}>
<input placeholder="Password" type="password" />
</div>
<div>
<button>Login</button>
</div>
</div>
</div>
)
}
};
export default Login;

Since we will need some basic styling, add the following styles to your Login.module.css file so that we can access them on the classes import:

.loginPage {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.form {
background: #f1f1f1;
padding: 10%;
border-radius: 5px;
box-shadow: 2px 2px 5px #ccc;
}
.formInput input {
padding: 5px;
margin-bottom: 10px;
border-radius: 5px;
border: 1px solid #ccc;
}
.form button {
padding: 5px;
min-width: 100%;
border-radius: 5px;
border: 1px solid #ccc;
}

If everything went right, your app should look like something as shown below:

The login form, currently it has no logic mapped to it

We will now set up a username and password in our state, and implement two way binding with the form.

...class Login extends Component {
state = {
username: '',
password: ''
}
...
}

Now to add a change handler to change our state for entered input:

inputChangeHandler = (e, inputType) => {
// e is the event received for input onChange
if (inputType === 'uname') {
this.setState({ username: e.target.value });
} else {
this.setState({ password: e.target.value });
}
};

And to attach this change handler to the inputs in our JSX:

...<div className={classes.formInput}>
<input
placeholder="Username"
value={this.state.username}
onChange={
(event) => this.inputChangeHandler(event, 'uname')
}

/>
</div>
<div className={classes.formInput}>
<input
placeholder="Password"
type="password"
value={this.state.password}
onChange={
(event) => this.inputChangeHandler(event)
}

/>
</div>
...

We mapped the value for both the inputs to our state’s username and password, and added the inputChangeHandler( ) method to the onChange event which is triggered for every keystroke.

You should now be able to edit the username and password directly, and the changes being updated in the state.

Setting up two way binding in our login page

Great, now we just have to trigger a function which allows us to login when we click the Login button.

First, let us enable the button only when both the username and password fields are present. We can do this by using ternary operators:

...<button
disabled={
!(this.state.username && this.state.password)
}

>
Login
</button>
...

Now to make a basic function which is triggered when we click the button. For now, we will just add an alert to it. In your Login.js file, add this method:

loginHandler = () => {
// using template strings
alert(`Hello, ${this.state.username}`);
};

And wire this method to your login button:

...<div>
<button
disabled={
!(this.state.username && this.state.password)
}
onClick={this.loginHandler}
>
Login
</button>
</div>
...

On entering a username and password and clicking this button, you should now see an alert with your username in the greeting.

We will later be modifying this method to implement an actual login functionality.

First, we will need a mock back end. This is a very useful tool in web development, since you cannot have your front end development blocked and awaiting API implementations in a real project.

We will mock a login response, complete with an auth token. Sounds complicated? Pretty easy to implement though!

We will be using json-server, a very popular mock server. Install it using the following command:

yarn add json-server

Now we will create a mocks folder in our src folder, where we will house all the files related to mocking data, and create a file called server.js inside it.

The server.js file at the path src/mocks

To begin, add the following line of code to you server.js file:

// we will require the file
const jsonServer = require('json-server');
// obtain an instance of a server
const server = jsonServer.create();
// SERVER ON PORT NUMBER AS SPECIFIED HERE
server.listen(4000, () => {
console.log('JSON Server is running');
});

We are basically wrapping over a node.js server using this package, hence the require instead of the import we saw so far. We create an instance of the server, and use the listen( ) method to pass in a port to listen on and a message to display once the server is up.

Now execute this file as follows, from your terminal:

We start our server with the help of node, by specifying the path to the file to be executed

As expected, we see our message in the terminal window. Congratulations, our server is up and running!

Let us move on to creating a response to login requests. For this we will need middlewares, which you can think of as methods which execute between the point when a request is received by the server and before the response is sent. We can use the received request in this interval to create the desired response.

First, let us create another file in the mocks folder called endpoints.js which we will use to have all our endpoints and their responses for now.

exports.postLogin = {
endpoint: '/login',
data: {
username: 'akant'
},

response: {
token: 'my-hero-token',
}

};

We will use the data property to verify just the username, we could verify password as well but since we are demonstrating mocking and are aiming for development convenience for the sake of this blog, we’ll just go for the username for now. You can put in a different username here instead of ‘akant’.

We will also later use the response property to send back a mock token, to emulate an actual auth token.

Now, to make the following changes in our server.js file:

// we will require the file
const jsonServer = require('json-server');
const middlewares = jsonServer.defaults();
// obtain our request endpoints
const endpoints = require('./endpoints');
// obtain an instance of a server
const server = jsonServer.create();
// wire up the default node middlewares
server.use(middlewares);
// parse the body using bodyParser middleware
server.use(jsonServer.bodyParser);
// LOGIN ROUTE - POST
const { postLogin } = endpoints;
server.post(postLogin.endpoint, (req, res, next) => {
const { username } = req.body;
const responseObj = (
postLogin.data.username === username
? postLogin.response.token
: null

);

if (responseObj) {
res.send(responseObj);
} else {
res.status(401);
res.send({
errorMsg: 'unauthorized',
code: 401
});
}
next();
});
// SERVER ON PORT NUMBER AS SPECIFIED HERE
server.listen(4000, () => {
console.log('JSON Server is running');
});

Let us go through these step by step, first the following two lines:

...
const middlewares = jsonServer.defaults();

...
server.use(middlewares);
server.use(jsonServer.bodyParser);
...

On our instance of the server: jsonServer we use the defaults( ) method to obtain the default middlewares for our server. These are middlewares which we would otherwise have to configure ourselves, such as those enabling CORS policies.

The server.use( ) method wires up these necessary middlewares to our server. Every time we receive our request in the server, it is funnelled through these middlewares and transformed.

Another such middleware is the bodyParser middleware, which helps to automate the process of extracting the request data into buffers which can then be read in the server. Using this, we can directly start using the data from the request.

The next significant change is:

...server.post(postLogin.endpoint, (req, res, next) => {
const { username } = req.body;
const responseObj = (
postLogin.data.username === username
? {token : postLogin.response.token}

: null
);

if (responseObj) {
res.send(responseObj);
} else {
res.status(401);
res.send({
errorMsg: 'unauthorized',
code: 401
});
}
next();
});
...

The server.post( ) method is used to transform POST requests. We pass to it the route for the request, and the callback to execute as middleware on the request. Hence, in the callback we receive the args req, res, next where req is the request, res is the response to send back and next is used to move on to the next middleware if there are any. Since middlewares catch a request in the server to transform it, it is necessary to call next( ) so that we can pass the request out of the middlewares to be actually sent back.

You can try excluding next( ) to see what happens.

Thanks to the middleware we used before, bodyParser, we can easily extract our username from the request body as demonstrated in the line:

const { username } = req.body;

We then refer to the postLogin object we exported from our endpoints file, to match the username with the one specified in the data property. If found, we will send back the mock token from the response property:

const responseObj = (
postLogin.data.username === username
? {token : postLogin.response.token}
: null
);

The res.send( ) method helps us send our intended response, which will be the mock token if the username matched in the previous step. Otherwise, we will send a status code of unauthorised to show that we were not able to authenticate the login request based on our auth logic.

if (responseObj) {
res.send(responseObj);
} else {
res.status(401);
res.send({
errorMsg: 'unauthorized',
code: 401
});
}

Let us see this in action now, by making a request from our front end. Remember, our server is running on port 4000 as specified before:

...
server.listen(4000, () => {
console.log('JSON Server is running');
});

We’ll need to restart our server for the changes to take effect:

node src/mocks/server.js

First, we will go to our src/Containers/Login folder and open the Login.js file. In this file, we already made a basic loginHandler( ) method. Modify it as follows to make our login request:

loginHandler = () => {
// creating the request object
const reqObj = {
username: this.state.username
};
// we will need to stringify this object for fetch
fetch('http://localhost:4000/login', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reqObj)
}).then(response => console.log(response));

};

We are using Javascript’s fetch API to make our request. We pass in the request URL, along with the configuration object which defines our method of request, the type of data we are sending and the data itself.

Try logging in now with the correct username, and check your browser’s network request, by going into the network tab in your browser dev tools:

The token we sent, received as a response

We will receive the token in the response, as expected. In a RESTful application, we use such authtokens to authenticate and authorise user requests in the back end. We send these requests in the Authorization header for every request to a protected route in the back end. We will also use this token to check if our user is logged in or not, so that we can decide if we want to show them the protected pages of our web app.

Therefore, we will need to parse this token we receive as response and store it at a place from where we can attach it to every request we make to our server. We will also have to ensure that this token persists across page refreshes, since on every refresh we create a new instance of our web app.

Let us divided these tasks into two steps: parsing and storing our token in the app, and persisting our token across page refreshes.

Parsing the token and storing it in the app

Assuming our knowledge is limited to only the methods available to us discussed so far in this series of blogs, we might think of storing the token in our state for the Login component or the App component. Let us decide to store it in our App component, as we might later need to distribute this token from a central place, or a central store you could say, to other components where we need to verify whether a user is logged in or not.

For this, we will add a state to our App component:

state = {
isAuth: false,
token: null,

};

Add a method to toggle this state:

loginHandler = (authFlag, token=null) => {
this.setState({
isAuth: authFlag,
token: token
});

};

And make the following changes to the render method to display a different page for the logged in state:

...render() {
return (
<div className="App">
{
this.state.isAuth
?
(
<div style={{textAlign: 'center'}}>
<h1>Heroes Home Page</h1>
<button
onClick={
() => this.loginHandler(false)
}

>
Logout
</button>
</div>
)
: <Login loginHandler={this.loginHandler} />
}
</div>
)
}
...

Whenever we set the isAuth state to true, we will render the logged in view. Otherwise we show our Login component. A pretty straightforward approach.

We pass the loginHandler to toggle auth state to out Login component, let us wire it up there as well.

In your Login.js file for the Login component, make the following changes:

loginHandler = () => {
// creating the request object
const reqObj = {
username: this.state.username
};
// we will need to stringify this object for fetch
fetch('http://localhost:4000/login', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reqObj)
})
.then(response => {
if(response.status === 401) {
throw new Error('Authentication failed');
}
const token = response.json();
return token
})
.then(response => {
this.props.loginHandler(true, response.token);
})
.catch(err => {
alert(err);
this.props.loginHandler(false);
});

};

In case we receive an unauthorised response, we throw an error in our first then and show an alert for it using the catch block. Otherwise, we transform the response using the .json( ) method, and forward it to the next then block. We then use loginHandler we passed as a prop from App.js, to set the login status.

On successful login

Cool, we have our basic application set up. But wasn’t it so much effort to transform requests like this on the front end, and it kind of looks ugly seeing something like this in the middle of our component code? There has to be a better way! Let us find this out, by using a package called axios.

We will add axios to our project, by using yarn. This package will help make our lives easy when it comes to making API calls in react:

yarn add axios

After successfully adding axios, we can import it from the axios package as follows (in the Login.js component file):

import axios from 'axios';

Now all we have to do is replace fetch with our new implementation. You can see how much more concise it becomes:

loginHandler = () => {
// creating the request object
const reqObj = {
username: this.state.username
};
axios.post('http://localhost:4000/login', reqObj)
.then(response => {
const token = response.data;
this.props.loginHandler(true, token);
})
.catch(err => {
alert(err);
this.props.loginHandler(false);
});

};

By using axios, we no longer need to manually parse the request and response as we needed to with fetch. We can also extract the required properties from the response body more easily, and for failed requests, such as unauthorised calls we no longer have to manually throw errors.

We will see how axios will make it easier in many other ways as well to manage API calls, as we progress further in this blog.

Another thing to note is that on refresh, we lose our logged in state since the app is reinitialised, so we need to save our token somewhere that allows it to persist.

Persisting our token across page refreshes
The way we are going to do this, is save the token in our browser’s session storage so that we have access to the token even when the page refreshes.

For this, we will first store our token in session storage on successful logins. Change the loginHandler of your App component as follows:

loginHandler = (authFlag, token = null) => {
this.setState({
isAuth: authFlag,
token: token
});
if (token) {
sessionStorage.setItem('token', token.token);
}
if (!authFlag) {
// to remove token on logout
sessionStorage.removeItem('token');
}

};

Just added an if check to set a token in the session storage.

Now, we know on refresh our components are going to be mounted again, so we’ll set up our state using a constructor for our App component:

constructor(props) {
super(props);
const token = sessionStorage.getItem('token');
if(token) {
this.state = {
isAuth: true,
token: token
};
} else {
this.state = {
isAuth: false,
token: null
};
}

}

After making these changes, you should be able to refresh your app without logging yourself out due to loss of state data.

App persists through refresh, we log out on clicking logout button

Great, let us move onto the next part of fetching a list of heroes. We will serve this list from our mock server.

This will be a simple GET request, so pretty straightforward to implement. First, we will create a JSON file with all our hero data. Create a folder called response inside your mocks directory:

Now add a file called heroList.js inside this folder with the following contents:

const heroList = [
{
name : "John Rambo",
skill : "Master Commando",
movie : "Rambo Series"
},
{
name : "Legolas",
skill : "Master Elf",
movie : "Lord of the Rings Series"
},
{
name : "T-800",
skill : "Terminator/Assassin",
movie : "Terminator Series"
},
{
name : "Black Mamba",
skill : "Master Assassin",
movie : "Kill Bill Series"
},
{
name : "Rick Deckard",
skill : "Detective",
movie : "Blade Runner"
},
{
name : "Ellen Ripley",
skill : "Astronaut Survivor",
movie : "Aliens"
}
];
module.exports = heroList;P.s. For the choice of heroes, these were just off the top of my head at the moment I was writing this.

Now to serve this file from our mock server, we will first extract this data to be sent in our request. We will do this in our endpoints.js file in the mocks folder:

const heroList = require('./response/heroList');exports.postLogin = {
endpoint: '/login',
data: {
username: 'akant'
},
response: {
token: 'my-hero-token',
}
};
exports.getHeroes = {
endpoint: '/heroList',
response: {
heroes: heroList
}
};

And, to serve this data we will implement the following middleware in our server.js file :

const { getHeroes } = endpoints;...server.get(getHeroes.endpoint, (req, res, next) => {
const token = req.headers.token;
if (token) {
res.send(getHeroes.response);
} else {
res.status(401);
res.send({
errorMsg: 'unauthorized',
code: 401
});
}
next();
});

We use the server.get( ) method to handle GET requests made to the server. Also note that we have put a check to expect our token as a part of the request headers.

In case the token is not present, we will send back an unauthorized status (401) code.

Let us now create our front end request for this. First, we will replace the following JSX in our App component:

render() {
return (
<div className="App">
{
this.state.isAuth
? (
<div style={{ textAlign: 'center' }}>
<h1>Heroes Home Page</h1>
<div>
<button
onClick={
() => this.loginHandler(false)
}
>
Logout
</button>
</div>
</div>

)
: <Login loginHandler={this.loginHandler} />
}
</div>
)
}

In your Containers folder, create a new folder called HomePage. Inside this folder, add a file called HomePage.js:

import React, { Component } from 'react';class HomePage extends Component {
render() {
return (
<div style={{ textAlign: 'center' }}>
<h1>Heroes Home Page</h1>
<div>
<button
onClick={
() => this.props.loginHandler(false)
}

>
Logout
</button>
</div>
</div>
)
}
};
export default HomePage;

We have basically moved the previous JSX into a separate class based component. We will also be passing the loginHandler( ) method as a prop.

Now import this in your App component and add it to your code, in App.js :

...
import HomePage from './Containers/HomePage/HomePage';
...
render() {
return (
<div className="App">
{
this.state.isAuth
? (
<HomePage loginHandler={this.loginHandler}/>
)
: <Login loginHandler={this.loginHandler} />
}
</div>
)
}
...

Your application should be working exactly as before. Great, now to finally make a request to fetch our list of heroes. Remember our class based components have access to a bunch of lifecycle methods? We will be using one of these lifecycle methods to make our network request, in our HomePage component.

In your HomePage.js file, add the following code:

...class HomePage extends Component {  componentDidMount() { 
// obtain token from props
const token = this.props.token;
if (token) {
axios.get('
http://localhost:4000/heroList', {
headers: {
token: token
}
})
.then(response => {
console.log(response.data);
})
.catch(err => {
console.log(err);
});
}
}
...}...

And to pass the token as a prop from our App component:

...<HomePage 
loginHandler={this.loginHandler}
token={this.state.token}
/>
...

Now check your console in the browser, you should be getting back your list of heroes whenever your HomePage is rendered:

Getting back the list of heroes from our mock back end

Also, since we used our token to protect our route, visiting the path without a token yields the following result:

Unauthorized check for when token is absent

We will be logging the user out whenever we receive this status code. We can do this by just tweaking our componentDidMount( ) code a little in the HomePage component:

componentDidMount() {
const token = this.props.token;
if (token) {
axios.get('http://localhost:4000/heroList', {
headers: {
token: token
}
})
.then(response => {
console.log(response.data);
})
.catch(err => {
console.log(err);
if (err.status === 401) {
this.props.loginHandler(false);
}

});
}
}

Try clearing your token manually from the browser devtools and refreshing the page. You can find it under the ‘Application’ header in your devtools:

Try clearing this token and refreshing the page, you will be logged out automatically

Okay, so now let’s try and populate our list of heroes in the UI. In your HomePage component, first we’ll add a state:

state = {
heroes: []
};

Next, we’ll update the state when we fetch our data successfully:

componentDidMount() {
const token = this.props.token;
if (token) {
axios.get('http://localhost:4000/heroList', {
headers: {
token: token
}
})
.then(response => {
console.log(response.data);
this.setState({ heroes : response.data.heroes });
})
.catch(err => {
console.log(err);
if (err.status === 401) {
this.props.loginHandler(false);
}
});
}
}

And conditionally render our output using JSX:

render() {
return (
<div style={{ textAlign: 'center' }}>
<h1>Heroes Home Page</h1>
<div>
{this.state.heroes.length
? (
this.state.heroes.map(hero => (
<div>
<h3>
{ hero.name }
<small>
{ hero.movie }
</small>
</h3>
<p>{ hero.skill }</p>
</div>
))
)
: null}

</div>
<div>
<button
onClick={
() => this.props.loginHandler(false)
}
>
Logout
</button>
</div>
</div>
)
}

It would be better to move the individual hero details JSX into its own, purely representational component. Create a new folder called Components in your root src/ folder, at the same level as the Containers folder:

Component folder at src/Components

We will keep all stateless components in this folder. Create a HeroCard component folder inside Components, and add a HeroCard.js file to it:

import React from 'react';const heroCard = (props) => {
const { hero } = props;
return (
<div>
<h3>
{ hero.name }
<small>
{ hero.movie }
</small>
</h3>
<p>{ hero.skill }</p>
</div>
)
};
export default heroCard;

Now importing this functional component into our HomePage component, and passing the hero’s details as props:

...import HeroCard from '../../Components/HeroCard/HeroCard';...render() {
return (
<div style={{ textAlign: 'center' }}>
<h1>Heroes Home Page</h1>
<div>
{this.state.heroes.length
? (
this.state.heroes.map(hero => (
<HeroCard hero={hero} />
))
)

: null}
</div>
<div>
<button
onClick={
() => this.props.loginHandler(false)
}
>
Logout
</button>
</div>
</div>
)
}
...

And we should have a view like this one:

The styling looks a little messed up, let’s fix it a bit. We’ll use inline style for brevity. In your HeroCard component, transform the returned JSX as follows:

...  return (
<div
style={{
border: '1px solid #ccc',
margin: '0 auto',
width: '20%'
}}

>
<h3>
{ hero.name }
</h3>
<div>
<small>
{ hero.movie }
</small>
</div>

<p>{ hero.skill }</p>
</div>
)
...

We should now be able to distinguish our heroes a little better in the view:

Cool, now that we have our UI organised a little better let’s start work on displaying our villain’s data in a separate page. But we will first need to add routing to our application for this.

So far, we were showing our components conditionally in App.js:

...{
this.state.isAuth
? (
<HomePage
loginHandler={this.loginHandler}
token={this.state.token}
/>
)
: <Login loginHandler={this.loginHandler} />
}
...

This has a few problems: we are not actually routing, just replacing elements, we are using an if check so we can only toggle between two components, we do not have a unique URL for visiting different pages etc.

Let us solve this problem first, by implementing routing. Since we will need to display a third, dedicated page to an individual hero.

Routing

We will use the react-router package to implement routing in our application. To add it to your project, simply run:

yarn add react-router-dom

Once installed, go to your src/index.js file and add the following:

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

We are basically wrapping our main App component with the BrowserRouter. The BrowserRouter component will provide the context for whatever route we are visiting in our application.

Now, to map our application’s paths to our Components. Open your App component, and first import the following:

import { Switch, Route } from 'react-router-dom';

The Switch component makes sure only one route is loaded at a time, and the Route component is used to render a component for a specific path visited in the browser.

render() {
return (
<div className="App">
{
this.state.isAuth
? (
<Switch>
<Route path="/homePage" exact>
<HomePage
loginHandler={this.loginHandler}
token={this.state.token}
/>
</Route>
<Route path="/">
<h1>Page Not Found</h1>
</Route>
</Switch>
)
: (
<Switch>
<Route path="/">
<Login loginHandler={this.loginHandler} />
</Route>
</Switch>
)
}
</div>
)
}

We are basically rendering two versions of our routes: one for the unauthenticated app, and one for the authenticated app. The path prop on the Route component matches the current path we are on in the browser.

An important thing to note is that react-router does partial matching of paths. Hence, we always visit the ‘/’ path. Quoting from their official docs:

One important thing to note is that a <Route path> matches the beginning of the URL, not the whole thing. So a <Route path="/"> will always match the URL. Because of this, we typically put this <Route> last in our <Switch>. Another possible solution is to use <Route exact path="/"> which does match the entire URL.

An exact prop helps us to narrow down whenever we visit a specific page, as we have provided for the HomePage component. Also, since we always visit the ‘/’ path, we can catch all other routes which we have not configured and show a ‘Page Not Found’ message to the user. Take care to place this route at the end of your Switch block though!

Try playing around with your application a bit and see how things look so far.

We can make our solution more elegant, by using a Redirect component provided by the routing package instead of the ‘Page Not Found’ approach:

import { Switch, Route, Redirect } from 'react-router-dom';...  <Switch>
<Route path="/homePage" exact>
<HomePage
loginHandler={this.loginHandler}
token={this.state.token}
/>
</Route>
<Redirect from="/" to="/homePage" />
</Switch>
...

This way, whenever we visit a path which is not configured we are redirected to our homepage.

Cool, let us now fetch and display a list of villains too. If you are feeling adventurous enough, try configuring the path to fetch villains in our mock server yourself. It is almost the same as the approach we used for heroes, just the list data and path names will differ.

So let’s begin by first adding a list of villains to the mocks/response folder. Create a file called villainList.js:

const villainList = [
{
name : "Sheriff",
skill : "Sheriff",
movie : "Rambo First Blood"
},
{
name : "Sauron",
skill : "Pure Evil",
movie : "Lord of the Rings Series"
},
{
name : "T-1000",
skill : "Terminator Assassin",
movie : "Terminator Series"
},
{
name : "Cottonmouth",
skill : "Master Assassin",
movie : "Kill Bill Series"
},
{
name : "Roy",
skill : "Renegade/Replicant",
movie : "Blade Runner"
},
{
name : "Alien",
skill : "Being an Alien",
movie : "Aliens"
}
];
module.exports = villainList;

Now to add this to our endpoint.js file:

const villainList = require('./response/villainList');...exports.getVillains = {
endpoint: '/villainList',
response: {
villains: villainList
}
};

And making a back end route to send this information back, in our server.js file:

...// GET VILLAIN LIST ROUTE
const { getVillains } = endpoints;
...// GET VILLAIN ROUTE HANDLER
server.get(getVillains.endpoint, (req, res, next) => {
const token = req.headers.token;
if (token) {
res.send(getVillains.response);
} else {
res.status(401);
res.send({
errorMsg: 'unauthorized',
code: 401
});
}
next();
});

Now all that’s left to do is create another page where we will display this list of villains.

We can reuse our HomePage for this, but we will create a separate container to check out routing in detail.

First, let us create a central navbar which we can use to move between these two pages.

Create a new component Navbar- create a folder Navbar in your Components directory and add a Navbar.js file to it:

import React from 'react';
import { NavLink } from 'react-router-dom';
const navbar = (props) => {
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
width: '50%',
margin: '0 auto',
padding: '20px',
textAlign: 'center'
}}
>
<div>
<NavLink to="/homePage">Heroes</NavLink>
</div>
<div>
<NavLink to="/villains">Villains</NavLink>
</div>
<div>
<button
onClick={
() => props.loginHandler(false)
}
>
Logout
</button>

</div>
</div>
)
};
export default navbar;

We use the NavLink component from our routing package to create links to our different routes. Another advantage of using NavLink is that we can add custom css classes for the currently active route as we will see in a bit. Also note that we have moved the Logout button from out HomePage component to here, using props yet again to trigger logout.

Next in the App component,

import Navbar from './Components/Navbar/Navbar';

and the following changes in the authenticated view block (still in App.js):

<>
<Navbar loginHandler={this.loginHandler}/>
<Switch>
<Route path="/homePage" exact>
<HomePage
loginHandler={this.loginHandler}
token={this.state.token}
/>
</Route>
<Redirect from="/" to="/homePage" />
</Switch>
</>

We pass our loginHandler as a prop to handle logout. You should end up with a view like this:

We will now create another container component for the villains. It will be similar to the HomePage container component. Create a file in the directory src/Containers/VillainPage, and name it VillainPage.js:

import React, { Component } from 'react';
import HeroCard from '../../Components/HeroCard/HeroCard';
import axios from 'axios';
class VillainPage extends Component {
state = {
villains: []
};
componentDidMount() {
const token = this.props.token;
if (token) {
axios.get('http://localhost:4000/villainList', {
headers: {
token: token
}
})
.then(response => {
console.log(response.data);
this.setState({ villains : response.data.villains });
})
.catch(err => {
console.log(err);
if (err.status === 401) {
this.props.loginHandler(false);
}
});
}
}
render() {
return (
<div style={{ textAlign: 'center' }}>
<h1>Villains</h1>
<div>
{this.state.villains.length
? (
this.state.villains.map(villain => (
<HeroCard hero={villain} />
))
)
: null}
</div>
</div>
)
}
};
export default VillainPage;

We are still reusing the HeroCard component, only passing our villains to it instead. The other changes are small, and are highlighted above.

Import this in your App component and add a route to reach this component:

import VillainPage from './Containers/VillainPage/VillainPage';...<>
<Navbar loginHandler={this.loginHandler}/>
<Switch>
<Route path="/homePage" exact>
<HomePage
loginHandler={this.loginHandler}
token={this.state.token}
/>
</Route>
<Route path="/villains" exact>
<VillainPage
loginHandler={this.loginHandler}
token={this.state.token}
/>
</Route>

<Redirect from="/" to="/homePage" />
</Switch>
</>
...

You should now be able to switch between routes using the navbar:

Now we’ll just style the links a bit to show the active route. To do this, the NavLink component has an activeClass prop which we can use. In your Navbar.js component:

import classes from './Navbar.module.css';...<div>
<NavLink
activeClassName={classes.active}
to="/homePage"
>
Heroes
</NavLink>
</div>
<div>
<NavLink
activeClassName={classes.active}
to="/villains"
>
Villains
</NavLink>
</div>
...

And defining the class in Navbar.module.css file for the component:

.active {
color: red;
}
The active route will now be highlighted

That should cover the basics of routing, enough for the scope of this blog. You can also change your routes via code, let’s see this for the VillainPage:

import { withRouter } from 'react-router-dom';...render() {
console.log('props', this.props);
...
}
...export default withRouter(VillainPage);

By wrapping our VillainPage export with the withRouter higher order component, we are able to obtain some additional props provided by our router:

The history, location and match props. You can access them in your code to play around with your routing logic, such as adding a button which can change routes. For example, as shown below for the VillainPage component under the JSX for the heading:

...<h1>Villains</h1>
<div>
<button
onClick={
() => this.props.history.push('/homePage')
}
>
Example
</button>

</div>
...

On clicking this button, you will be taken to the homepage. There are many useful things you can do with these, check out the official docs here.

Okay, so we have covered most of the basics of APIs and routing, just one thing left. Organising our API calls in a better way.

Axios instances
Till now, we were using our axios package in its most raw form. However, it provides a lot of convenient ways to make our API calls more organised.

Let us have a look how. Create a folder src/axios and add a file axios.js to it.

Inside the file:

import axios from 'axios';const instance = axios.create({
baseURL: 'http://localhost:4000',
});
export default instance;

By doing this, we create a single instance of our axios object which we can use across our components. Here, we have defined a base URL so that we don’t have to type it in again and again everywhere like we had to in the past. We will only have to specify the relative path we want to use, by importing this instance. Let’s do this for the VillainPage component. First let us change the import to now use this instance:

import axios from '../../axios/axios';

And now to provide the relative route:

componentDidMount() {
const token = this.props.token;
if (token) {
axios.get('/villainList', {
headers: {
token: token
}
})
.then(response => {
console.log(response.data);
this.setState({ villains : response.data.villains });
})
.catch(err => {
console.log(err);
if (err.status === 401) {
this.props.loginHandler(false);
}
});
}
}

Check your browser and things should still look as they did before. Only now we have made a central base URL which we use and which we don’t have to type out every time. We also have to send tokens with every protected request to the back end, this can also be centralised by using what we call interceptors in axios. These can be used for intercepting requests and responses, both.

We will add our token by intercepting the request using axios before it goes out. This can be done as follows, in your axios.js file:

...const instance = axios.create({
baseURL: 'http://localhost:4000',
});
instance.interceptors.request.use(
(config) => {
const token = sessionStorage.getItem('token');
if (token) {
config.headers.token = token;
}
return config;
},
(error) => Promise.reject(error),
);
...

And remove the headers from your request now, in your VillainPage component:

...componentDidMount() {
const token = this.props.token;
if (token) {
axios.get('/villainList')
.then(response => {
console.log(response.data);
this.setState({ villains : response.data.villains });
})
.catch(err => {
console.log(err);
if (err.status === 401) {
this.props.loginHandler(false);
}
});
}
}
...

Using this, for every request made using our instance as we did in VillainPage, a token will automatically be attached to the request headers. To know more about interceptors, have a look here.

You could create one to check for a 401 status code on all responses, and log out the user automatically by removing the token from session storage in case it is encountered. This will make our network call block even more concise, as we are currently checking for the status code in the catch block as shown above. Having these checks at a single place also reduces the scope for bugs.

So that was it for another lengthy blog post on some basic things for creating a react web application and probably soon, these posts will get shorter as we get done with the conventional ways of creating react apps!

Happy coding!

--

--

Arun Kant Pant

Building things, breaking things, learning things. Frontend Engineer at The Economist Intelligence Unit.