Async Web API calls using React with Mobx
Development | Ivan Dobranic

Async Web API calls using React with Mobx

Tuesday, Apr 16, 2019 • 4 min read
We will learn how to use basic CRUD (create, read, update, delete) operations by combining React with Mobx.

Introduction

React is a JavaScript library for building user interfaces. Mobx is simple stage management library that provides mechanism to store and update the application state. Its most powerfull feature is that it can make your variables observable. It will observe for changes, then tell React to re-render the page and show changes instantly. Mobx observes references, not the values itself. For example, if you have object:

{
    name: "John",
    address : {
        city: "New York"
    }
    list: ["a", "b", "c"];
}

Mobx will observe properties: “name, address, city, list” and not the values: “John, New York, a, b, c”. We will be calling the server-side API asynchronously and then wait for results, making our web application more responsive. This frees up server’s CPU to perform other tasks while it waits for results. Instead of using promises and “.then” function, we will be using “async” and “await”, which is the approach I like more.

Basic Setup

I will assume that you have already Node.js and npm installed on your system and you know how to setup React. The next thing you need to do is to install Mobx. Just type in your folder project “npm install mobx –save” and “npm install mobx-react –save” on your cmd and you ready to go.

Creating a Service and a Store

First we will be creating a service for our HTTP calls to the Web API, and it will contain get, post, put and delete methods. Let’s create a country service:

const webApiUrl = "http://country.local/api/Country";

class CountryService {
    
    get = async (urlParams) => {
        const options = {
            method: "GET",
        }
     const request = new Request(webApiUrl + "?" + urlParams, options);
     const response = await fetch(request);
     return response.json();
    }
    post = async (model) => {
        const headers = new Headers();
        headers.append("Content-Type", "application/json");
        var options = {
            method: "POST",
            headers,
            body: JSON.stringify(model)
        }
        const request = new Request(webApiUrl, options);
        const response = await fetch(request);
        return response;
    }
    put = async (model) => {
        const headers = new Headers()
        headers.append("Content-Type", "application/json");
        var options = {
            method: "PUT",
            headers,
            body: JSON.stringify(model)
        }
        const request = new Request(webApiUrl, options);
        const response = await fetch(request);
        return response;
    }
    delete = async (id) => {
        const headers = new Headers();
        headers.append("Content-Type", "application/json");
        const options = {
            method: "DELETE",
            headers
        }
        const request = new Request(webApiUrl + "/" + id, options);
        const response = await fetch(request);
        return response;
    }

    export default CountryService;

Next we will create a Store and inject our service through Store’s constructor. Store is a place where you keep your reusable logic and application’s UI state that will be used by your components.

import { observable, runInAction, decorate } from 'mobx';
import CountryService from './CountryService'

class CountryStore {
constructor(){
 this.countryService = new CountryService();
}
 countryData = {
     model: []
 };
 status = "initial";
 searchQuery = "";

 getCountriesAsync = async () => {
     try {
         var params = {
             pageNumber: this.countryData.pageNumber,
             searchQuery: this.searchQuery,
             isAscending: this.countryData.isAscending
         };
         const urlParams = new URLSearchParams(Object.entries(params));
         const data = await this.countryService.get(urlParams)
         runInAction(() => {
             this.countryData = data;
         });
     } catch (error) {
         runInAction(() => {
             this.status = "error";
         });
     }
 };
 createCountryAsync = async (model) => {
     try {
         const response = await this.countryService.post(model);
         if (response.status === 201) {
             runInAction(() => {
                 this.status = "success";
             })
         } 
     } catch (error) {
         runInAction(() => {
             this.status = "error";
         });
     }

 };
 updateCountryAsync = async (vehicle) => {
     try {
         const response = await this.countryService.put(vehicle)
         if (response.status === 200) {
             runInAction(() => {
                 this.status = "success";
             })
         } 
     } catch (error) {
         runInAction(() => {
             this.status = "error";
         });
     }
 };
 deleteCountryAsync = async (id) => {
     try {
         const response = await this.countryService.delete(id);
         if (response.status === 204) {
             runInAction(() => {
                 this.status = "success";
             })
         } 
     } catch (error) {
         runInAction(() => {
             this.status = "error";
         });
     }
 }
}

decorate(CountryStore, {
 countryData: observable,
 searchQuery: observable,
 status: observable
});

export default new CountryStore();

This is where Mobx kicks in. We decorate our variables observable and the magic happens - Mobx will track their state and apply changes instantly. For this magic to happen we also need to make an action to modify the state. I’ve chosen the run in action approach, but there are few other options. In this example I’m getting the countryData from the server that contains paging and sorting parameters, and then I’m sending that same data back through urlParams when it’s state changes (server side paging and sorting is used). Along with it I’m sending a searchQuery from the UI. Create, Update and Delete functions are simple, I’m passing a model and id.

Components

Next we will create components for our CRUD methods. Components let you split the UI into independent, reusable pieces. In other words a component is a class or function that accepts inputs(props);

import * as React from 'react';
import { observer, inject } from 'mobx-react';

class CountryList extends React.Component {

    componentDidMount() {
        this.props.CountryStore.getCountriesAsync();
    }

    searchCountry = (e) => {
        if (e.key === "Enter") {
            this.props.CountryStore.search = e.target.value;
        }
    }

    sortCountry = () => {
        this.props.CountryStore.countryData.isAscending = this.props.CountryStore.countryData.isAscending 
        ? false : true
        this.props.CountryStore.getCountriesAsync()
    }
    render() {
        return (
            <div>
                <input type="search" placeholder="Search" onKeyPress={this.searchCountry} />
                <input type="submit" value="Sort" onClick={this.sortCountry}/>
                <table>
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>City</th>
                            <th>Date Created</th>
                        </tr>
                    </thead>
                    <tbody>
                        {this.props.CountryStore.countryData.model.map(country => (
                            <tr key={country.id}>
                                <td>{country.name}</td>
                                <td>{country.city}</td>
                                <td>{country.dateCreated}</td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </div>
        )
    }
}

export default inject("CountryStore")(observer(CountryList));

Here we are calling the get function in componentDidMount. It’s one of React’s lifecycle methods and it’s invoked immediately after a component is mounted. This is a good place for calling your Web API. We are calling the CountryStore methods and properties with this.props. CountryStore is injected after the class scope and class is decorated to be an observer. Observers make sure that any data that is used during the rendering of a component forces a re-rendering upon change.

Methods for post and put are similar. Note that you need to implement have OnChange event if you gonna change existing text on your input fields.

class Create extends React.Component {
    CreateCountry = (e) => {
        e.preventDefault();
        this.props.CountryStore.createCountryAsync({
            name: this.refs.name.value,
            city: this.refs.city.value,
        });
        this.refs.name.value = null;
        this.refs.city.value = null;
    };
    render() {
        return (
            <div>
                <div>
                    <form onSubmit={this.CreateCountry}>
                    <div className="form-group">
                        <input ref="name" id="name" type="text" placeholder="Name"/>
                    </div>
                    <div className="form-group">
                        <input ref="city" id="city" type="text" placeholder="City"/>
                    </div>
                        <button type="submit">Save</button>
                    </form>
                </div>
            </div>
        )
    }
}

export default inject("VehicleMakeStore")(observer(Create)); 

Delete method is very simple, you just pass id of an entity to delete:

   Delete = (e) => {
        e.preventDefault();
        this.props.CountryStore.deleteCountryAsync(this.props.id)
    }

In this post we have learned how to implement basic CRUD operations using React and Mobx. Many people use Redux for state management on React, but for me Mobx is simpler and cleaner way to go with React.