In this article, we are going to learn how to use Axios with React to make API requests and also how to handle the response.
We’d learn this by building a simple blog using a fake API server.
Axios is a JavaScript library built for making HTTP requests from NodeJS or the browser. These include POST, GET, and many other forms of requests. It can be be used in vanilla Javascript as well as many other frameworks but our use-case today is React.
Communicating with APIs before was usually done with the fetch API. Axios doesn’t replace this but only makes the communication easier. It also has better error handling, easy header configurations and the readability of code is improved.
The features of Axios according to the NPM documentation are:Make
XMLHttpRequests
from the browserMake
HTTP requests from node.js Automatic transforms for JSON dataTransform request and response dataYou can get more features in the npm documentation.
We’d be using a popular public API – http://jsonplaceholder.typicode.com/. We can get a list of posts by hitting the /posts endpoint, edit and delete posts by /post/id and so on. You can check the site to see how to use their endpoints.
To use Axios in React, you’d need to install the package. Before you do that, we need our project folder. Quickly set up a project using create-react-app.
Install the package –
npm install -g create-react-app
Create the project –
create-react-app axios-with-react
Change directory –
cd axios-with-react.
Start server –
npm run start
Go to the browser and enter
localhost:3000
, you’d seeproject which the package sets for us.
npm install axios --save
Axios handles request methods such as
POST
, GET
, PUT
, DELETE
, PATCH
, etc. We’d be seeing some of the actions in our project.Change the contents of App.js to the following
import React from 'react';
import './App.css';
import axios from 'axios';
class App extends React.Component {
state = {
id: '',
title: '',
body: '',
data: []
}
componentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
let newData = res.data.slice(0,5);
this.setState({
id: newData[newData.length - 1].id + 1,
data: newData
}, () => console.log(this.state.id))
console.log(newData)
})
.catch(err => console.log("Couldn't fetch data. Error: " + err))
}
render() {
return (
<div className='ArticleContainer'>
<h1>Simple blog with React</h1>
<div className='AddArticle'>
<b>id of article: </b>
<input type='number' value={this.state.id} />
<form>
<input type='text' placeholder='Title' value={this.state.title} />
<textarea placeholder='Enter Body' value={this.state.body}>
</textarea>
<input type='submit' value='Add/Update Post'/>
</form>
</div>
{
this.state.data.length === 0 ?
<p>Loading Posts...</p>
:
this.state.data.map((post, index) => (
<article key={index}>
<h2>{index + 1}. {post.title}</h2>
<p>{post.body.substr(0, 100)}...</p>
<button className='delete'>Delete</button>
<button className='edit'>Edit</button>
</article>
))
}
</div>
)
}
}
export default App;
componentDidMount
is the safest stage in React components for making API requests. It is a cycle in a component’s life which ensures that the elements have been mounted to the DOM. Imagine making a request which would take a couple of time. It may keep our DOM empty until the request is completed.First, we have Loading posts showing on the screen until the data is fetched.
Check the console of your browser, you either get an error or the response from the endpoint we hit. From the response, we understand the structure and know-how to place the data on our frontend.
Notice we have only 5 results, that’s because we sliced the array. We are supposed to get over 100 results. Let’s add our CSS to see our beautiful frontend. Copy the following to App.css
* {
box-sizing: border-box;
}
.ArticleContainer {
width: 500px;
margin: 0 auto;
margin-top: 20px;
}
.AddArticle {
display: flex;
flex-direction: column;
width: 100%;
}
.AddArticle input, .AddArticle textarea {
width: 100%;
border-radius: 5px;
border: 1px solid #ddd;
margin: 5px 0;
padding: 5px;
}
.AddArticle input[type='submit'] {
padding: 5px;
background-color: greenyellow;
border: none;
cursor: pointer;
}
h1 { text-align: center; }
article {
padding: 10px;
border: 1px solid #ddd;
margin: 10px auto 20px;
width: 100%;
}
article h2 {
color: orange;
font-size: 30px;
margin: 5px 0;
}
article button {
width: 50px;
margin: 0 5px;
border: none;
padding: 5px;
cursor: pointer;
}
article .delete {color: red;}
article .edit {color:green;}
article .cancel {color: orange;}
We should have this;
We have five posts each with their id. The new article will have an id of 6. We can add more posts by making a post request to the server.
We have the endpoint https://jsonplaceholder.typicode.com/posts which allows us to create posts. Note that, as said on their website, the data would not be created on their server. It will only be faked as you’ll get a positive response for illustration purposes.
We have a button which handles creation and edition of posts – Add/Update Post button. I configured it this way so that we do not have to route to any other page to see all requests in action.
A method will be called by the button which will either add or update a post. In your code above, add the following methods immediately after the state declaration
...
changeId = e => {
let id = e.target.value;
this.setState({
id: id
})
}
changeTitle = e => {
let title = e.target.value;
this.setState({
title: title
})
}
changeBody = e => {
let body = e.target.value;
this.setState({
body: body
})
}
addOrUpdatePost = e => {
e.preventDefault();
if(this.state.title === '' || this.state.body === '' || this.state.id === '') {
alert('No field should be empty');
return;
} else if(this.state.id > this.state.data.length + 1) {
alert('Please use the next id');
} else {
if(this.state.data[this.state.id - 1] !== undefined) {
// update the post
} else {
// new post
axios.post("https://jsonplaceholder.typicode.com/posts", {
id: this.state.id + 1,
title: this.state.title,
body: this.state.body
})
.then(res => {
console.log(res);
let newPost = res.data;
let newData = [...this.state.data, newPost];
this.setState({
id: this.state.id + 1,
title: '',
body: '',
data: newData
});
})
.catch(err => console.log(err));
}
}
...
First, we have three methods that update the id, title, and body of the state respectively when called upon.
Next, we check to see if the states are empty which would result in an alert box, stating that “No field should be empty”We also confirm that the current id has either been used or just greater than the highest id with 1.
If it isn’t, we have an alert box which says “Please use the next id”A decision has to be made if a new post is to be created or a previous post is to be updated.
We check if the id exists by
this.state.data[this.state.id - 1] !== undefined
. If it returns undefined, it means the id doesn’t exist so we can create a new post there. We’d look at the update process soon. Let’s add these methods to our elements. Update your code to this
...
render() {
return (
<div className='ArticleContainer'>
<h1>Simple blog with React</h1>
<div className='AddArticle'>
<b>id of article: </b>
<input type='number' onChange={this.changeId} value={this.state.id} />
<form>
<input onChange={this.changeTitle} type='text' placeholder='Title' value={this.state.title} />
<textarea onChange={this.changeBody} placeholder='Enter Body' value={this.state.body}>
</textarea>
<input onClick={this.addOrUpdatePost} type='submit' value='Add/Update Post'/>
</form>
</div>
{
this.state.data.length === 0 ?
<p>Loading Posts...</p>
:
this.state.data.map((post, index) => (
<article key={index}>
<h2>{index + 1}. {post.title}</h2>
<p>{post.body.substr(0, 100)}...</p>
<button onClick={() => this.deletePost(index)} className='delete'>Delete</button>
<button onClick={() => this.editPost(index, post.title, post.body)} className='edit'>Edit</button>
</article>
))
}
</div>
)
}
...
When the add button is clicked, we use the post method to send our new post through the API.
Remember that it’s a fake live server, so it only returns a reponse which contains our new post as an object with a statusText of created. We add new data to the previous data and update the state.
When the component is re-rendered, our post will be the sixth post on the list.
Here is our response to the console.
Notice the
editPost
and deletePostmethod
in our code above. It allows us to edit or delete an individual posts. We’d come to that shortly.Our API gives us endpoints which we could use to update resources. For example, we could update the post with id 1, by hitting this endpoint – https://jsonplaceholder.typicode.com/posts/1
Let’s add the e
ditPostmethod
. Add the following immediately after the changeBodymethod
...
editPost = (postIndex, title, body) => {
this.setState({
id: postIndex + 1,
title: title,
body: body
})
}
...
The function takes the
postIndex
, title and body argument and resets the state. Remember that in setting the state, the input fields reflect the values. Also, remember that our button handles add and update. Since the id has been used before,
this.state.data[this.state.id - 1]
would not return undefined. We can also hit endpoints of posts and delete a post by their id. We already have the delete button so let’s add the method which it calls on clicking. Add the following before
componentDidMount
deletePost = postIndex => {
axios.delete(`https://jsonplaceholder.typicode.com/posts/${postIndex}`)
.then(res => {
let newData = [...this.state.data];
newData.splice(postIndex, 1);
this.setState({
id: newData.length + 1,
title: '',
body: '',
data: newData
})
console.log(res)
})
.catch(err => console.log(err));
}
The post is deleted when the delete method is called. The method takes the index, searches for it in our data and removes it thereby updating the state.
The whole code for the project (except the CSS);
import React from 'react';
import './App.css';
import axios from 'axios';
class App extends React.Component {
state = {
id: '',
title: '',
body: '',
data: []
}
changeId = e => {
let id = e.target.value;
this.setState({
id: id
})
}
changeTitle = e => {
let title = e.target.value;
this.setState({
title: title
})
}
changeBody = e => {
let body = e.target.value;
this.setState({
body: body
})
}
editPost = (postIndex, title, body) => {
this.setState({
id: postIndex + 1,
title: title,
body: body
})
}
addOrUpdatePost = e => {
e.preventDefault();
if(this.state.title === '' || this.state.body === '' || this.state.id === '') {
alert('No field should be empty');
return;
} else if(this.state.id > this.state.data.length + 1) {
alert('Please use the next id');
} else {
if(this.state.data[this.state.id - 1] !== undefined) {
axios.put(`https://jsonplaceholder.typicode.com/posts/${this.state.id}`, {
id: this.state.id ,
title: this.state.title,
body: this.state.body
}).then(res => {
let updatedData = [...this.state.data];
updatedData[this.state.id - 1] = res.data;
this.setState({
id: updatedData.length + 1,
title: '',
body: '',
data: updatedData
})
console.log(res)
})
.catch(err => console.log(err));
} else {
axios.post("https://jsonplaceholder.typicode.com/posts", {
id: this.state.id + 1,
title: this.state.title,
body: this.state.body
})
.then(res => {
console.log(res);
let newPost = res.data;
let newData = [...this.state.data, newPost];
this.setState({
id: this.state.id + 1,
title: '',
body: '',
data: newData
});
})
.catch(err => console.log(err));
}
}
}
deletePost = postIndex => {
axios.delete(`https://jsonplaceholder.typicode.com/posts/${postIndex}`)
.then(res => {
let newData = [...this.state.data];
newData.splice(postIndex, 1);
this.setState({
id: newData.length + 1,
title: '',
body: '',
data: newData
})
console.log(res)
})
.catch(err => console.log(err));
}
componentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
let newData = res.data.slice(0,5);
this.setState({
id: newData[newData.length - 1].id + 1,
data: newData
}, () => console.log(this.state.id))
console.log(newData)
})
.catch(err => console.log("Couldn't fetch data. Error: " + err))
}
render() {
return (
<div className='ArticleContainer'>
<h1>Simple blog with React</h1>
<div className='AddArticle'>
<b>id of article: </b>
<input type='number' onChange={this.changeId} value={this.state.id} />
<form>
<input onChange={this.changeTitle} type='text' placeholder='Title' value={this.state.title} />
<textarea onChange={this.changeBody} placeholder='Enter Body' value={this.state.body}>
</textarea>
<input onClick={this.addOrUpdatePost} type='submit' value='Add/Update Post'/>
</form>
</div>
{
this.state.data.length === 0 ?
<p>Loading Posts...</p>
:
this.state.data.map((post, index) => (
<article key={index}>
<h2>{index + 1}. {post.title}</h2>
<p>{post.body.substr(0, 100)}...</p>
<button onClick={() => this.deletePost(index)} className='delete'>Delete</button>
<button onClick={() => this.editPost(index, post.title, post.body)} className='edit'>Edit</button>
</article>
))
}
</div>
)
}
}
export default App;
We were working on a demo server, that’s why I had to manually update all properties of the state every time we make a request.
If we were using a server which gave us access to manipulate the data, all we’d have to do is make our requests and update the state to the current data. We wouldn’t have to filter by the index of the post.
Check out the npm documentation for more information about Axios.
I hope with this article, you have seen how easy it is making requests with Axios.
You can access CodeSource here.