DEV Community

Chan Ho Ahn
Chan Ho Ahn

Posted on • Edited on

Axios in React.Js

Axios is one of the most downloaded NPM package even though React.JS comes with its native FETCH API which supposedly does the same job as Axios does. I would not explain why Axios is more popular than FETCH or other APIs in both React.jS and Angular. This article is mostly about using Axios in React.JS which I have learned from Mosh Hamedani's training (https://programmingwithmosh.com).

The following is to help code CRUD method in React.js. It may be useful as a quick reference how to make it work with the backend Node.JS.

The following is full snapshot of the code structure except "render" part is omitted. The scope of this article is Axios and Node.JS CRUD implementation. "onClick" event for each handlers should be straightforward.

import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
const apiEndpoint = 'URL here';
class App extends Component {
    state = {
    posts: []
};

async componentDidMount() {
    const { data: posts } = await axios.get(apiEndpoint);
    this.setState({ posts });
}

handleAdd = async () => {
    const obj = { title: 'a', body: 'b' };
    const { data: post } = await axios.post(apiEndpoint, obj);
    const posts = [post, ...this.state.posts];
    this.setState({ posts });
};

handleUpdate = async post => {
    post.title = 'updated title';
    const { data } = await axios.put(apiEndpoint + '/' + post.id, post);
    // axios.patch(apiEndpoint + '/' + post.id, {title: post.title});
    const posts = [...this.state.posts];
    const index = posts.indexOf(post);
    posts[index] = post;
    this.setState({ posts });
};

handleDelete = async post => {
    await axios.delete(apiEndpoint + '/' + post.id);
    const posts = this.state.posts.filter(p => p.id !== post.id);
    this.setState({ posts });
};

Let's go over CRUD methods one by one.

GET Method : ComponentDidMount lifecycle hook is the most reasonable place to implement GET method. When the browser is ready with all components are ready, we can bring data from the backend side.

Axios returns "data" array in promise return. We can simply extract "data" from "Promise" return from Axios. Then, "data" is renamed to "posts" to simplify the code further. If {posts: posts} are the same value with 'posts' and 'post', we can simplify it to {posts} in ES6 JS.

async componentDidMount() {
    const { data: posts } = await axios.get(apiEndpoint);
    this.setState({ posts });
}

POST method is to add single object to the backend database. Assume that there are "title" and "body" properties in the data schema, then we want to add new object with {title:'a', body:'b'}.

In POST method, Axios returns the same return property of 'data' through Promise, but it is a single object which was just added via POST method. We take this object and add and update in "posts" in "state".

handleAdd = async () => {
    const obj = { title: 'a', body: 'b' };
    const { data: post } = await axios.post(apiEndpoint, obj);
    const posts = [post, ...this.state.posts];
    this.setState({ posts });
};

PUT or PATCH method is to update single item. The main difference between PUT and PATCH is PUT is more generic which allows us to update more than one properties. PATCH is more specific to update a single property in object. The following code technic I learned from Mosh was so useful and simple. setState in React is sometimes JS Array challenge as my experience. We can always find a way, but takes some effort to find a right and efficient way.

handleUpdate = async post => {
    post.title = 'updated title';
    const { data } = await axios.put(apiEndpoint + '/' + post.id, post);
    /* the following commented is for PATCH API as reference */
    // axios.patch(apiEndpoint + '/' + post.id, {title: post.title});
    const posts = [...this.state.posts];
    const index = posts.indexOf(post);
    posts[index] = post;
    this.setState({ posts });
};

DELETE method is pretty simple. In setState, a popular filter function in React is used.

handleDelete = async post => {
    await axios.delete(apiEndpoint + '/' + post.id);
    const posts = this.state.posts.filter(p => p.id !== post.id);
    this.setState({ posts });
};

All the above methods are based on "pessimistic updates" which means we want to ensure the backend update is updated before updating user view on the frontend side. The cons of this implementation is user may experience slow response from POST, PUT, DELETE methods.

Another way of implementation is "optimistic updates". Optimistic update is updating frontend first and running server side job at its background. This way can largely improve the responsiveness for user experience on the frontend side, however, we wishfully assume everything just works fine at the backend side.

We can still add a simple code change to rollback any changes made on the frontend side in case of a failure from the backend side. Here is a simple code change example with DELETE method.

handleDelete = async post => {
   /* store the current state in previousPosts, just in case of server side fail */
    const previousPosts = this.state.posts;
 /* optimistically, update on browser side, */
    const posts = this.state.posts.filter(p => p.id !== post.id);
 this.setState({ posts });

 /* server side update.  If any fail, rollback the state */
    try {
     await axios.delete(apiEndpoint + '/' + post.id);
     } catch (e) {
     this.setState({posts: previousPost});
   }

};

Personally, the above code with optimistic update with try-catch is the best fit for both user experience and recovery from any server side failure.

Reference: https://codewithmosh.com/

Top comments (0)