We're live-coding on Twitch! Join us!
React Async for Declarative Data Fetching

React Async for Declarative Data Fetching

Generally, there are two ways of writing code: Imperatively/Declaratively and two ways of fetching Data: Synchronously/Asynchronously. We are more convenient with making requests imperatively using fetch or libraries like axios.

With async-await, we get an opportunity to move from imperative asynchronous to imperative synchronous code style for fetching data. React is a library that makes it easy to build UIs declaratively. The key word is declarative. This is why there is a mixed pattern when we have both imperative code (for data fetching) and declarative code (for UI composition).

React-async provides a declarative API to perform any REST API call easily using a single React component. It takes care of handling errors, promise resolution and retrying promises and an excellent job at dealing with local asynchronous state.

Fetching Data Declaratively with React-Async

For better understanding let’s break down our topic into two parts. Declarative data fetching and Asynchronous API Calls in react.

Declarative data fetching is an approach used in calling APIS where you declare what you want it gets done for you without you worrying about all the things related to the call. It’s the opposite of the imperative approach where you also need to detail the steps, i.e. how to get what you need.

Since JavaScript is synchronous by default and is single threaded, when we want to render a component that shows some data coming from an asynchronous call before React 16, we were stuck with classes. We had to use the component lifecycle methods to ensure the call happens when the component is mounted, and then we use the local state to manage the loading state.

Asynchronous requests will wait for a request to respond while the rest of the code continues to execute. Then when the time is right, a callback will spring these asynchronous requests into action.

Essential Reading: Learn React from Scratch! (2019 Edition)

Let’s demonstrate this by making a call to an endpoint to grab a list of cryptocurrency prices.

import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
  state = {
    data: [],
    error: '',
  };

  componentDidMount() {     
    axios
      .get('https://api.coinmarketcap.com/v1/ticker/?limit=1')
      .then(res => this.setState({ data: res.data }))
      .catch(error => this.setState({ error }));
  }

  render () {
    return (
      <div className="App">
        <ul>
          {this.state.data.map(el => (
            <li>
              {el.name}: {el.price_usd}
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export default App;

N/B: Install axios by typing ‘npm install axios` in your terminal

Here we make our API call in the componentDidMount function to ensure it runs as soon as the component is loaded. We can make sense of our data only after we go through the following steps:

  • First, we make a request to the API
  • We receive a response
  • We extract data from the response
  • Then store the data in our local state

In the event of an error during the data fetching process:

  • We catch the error
  • Store the data in our local state

As you can see we explicitly spell out how and what to do at every step in fetching the data. Almost every React developer is familiar with the lines of code above. Though the syntax and functions work correctly, the code can be re-written to be more elegant and in fewer lines, if written declaratively. Let’s rewrite it using React-Async.

First, you need to install the package by typing npm install react-async in your terminal. Then write your component with the following code:

import React, { Component } from 'react';
import Async from 'react-async';

const loadJson = () =>
  fetch("https://api.coinmarketcap.com/v1/ticker/?limit=1")
    .then(res => (res.ok ? res : Promise.reject(res)))
    .then(res => res.json())

const App = () => (
  <Async promiseFn={loadJson}>
    {({ data, error, isLoading }) => {
      if (isLoading) return "Loading..."
      if (error) return ``Something went wrong: ${error.message}``

      if (data)
        return (
          <div>
             {data.map(el => (
              <li>
                {el.name}: {el.price_usd}
              </li>
             ))}
          </div>
        )

      return null
    }}
  </Async>
)

export default App;

However, since we are using CodeSandbox, we will add React-Async from the dependency menu.

Here we have rewritten our component using hooks instead of classes. We first create a function loadJson to handle our data fetching. Then, inside our App component, we utilize the Async component made available through the React-Async library.

Once our promise is resolved, props are made available to us to handle different scenario's.

  • isLoading is available so we can display a user-friendly message while the data is yet to be loaded.
  • error is available in case of an error during the fetch.
  • data is the actual data returned after the fetching is complete.

As we can see, we no longer have to use classes or lifecycle methods to load our data neither did we tell React-Async how to process the data or how to update our state.

React-Async manages the loading state through the isLoading fallback prop, which is rendered until data is ready to be rendered, that is when the dependent asynchronous call resolves and returns the data.

Helper Components

React-Async comes with several helper components that make your JSX more declarative and less cluttered. Each of the helper components will only render its children when appropriate. We can rewrite our App function to look like this:

const App = () => (
  <Async promiseFn={loadJson}>
    <Async.Loading>Loading...</Async.Loading>

    <Async.Resolved>
      {data => (
        <div>           
          {data.map(el => (
            <li>
              {el.name}: {el.price_usd}
            </li>
          ))}
        </div>
      )}
    </Async.Resolved>

    <Async.Rejected>
      {error => `Something went wrong: ${error.message}`}
    </Async.Rejected>
  </Async>
)

In the example above, we have utilized the **Async.Loading**, **Async.Resolved** and **Async.Rejected** functions to simplify our code and make it more readable. Helper components provided by React-Async can take a React element or a function as children. When you provide a function, you’ll receive render props you can use in your component.

More Helper Functions

Let’s build a small user profile app that utilizes some more helper functions. Update your component to the following code:

import React, { Component } from 'react';
import Async from 'react-async';

const loadUser = ({ userId }) =>
  fetch('`https://reqres.in/api/users/${userId}'`)
    .then(res => (res.ok ? res : Promise.reject(res)))
    .then(res => res.json())

const UserPlaceholder = () => (
  <div>
    <div>User Details Loading</div>
  </div>
)

const UserDetails = ({ data }) => (
  <div className="details">
    <img className="avatar" src={data.data.avatar} alt="" />
    <div>
      {data.data.first_name} {data.data.last_name}
    </div>
  </div>
)

const App = () => (
    <Async promiseFn={loadUser} userId={1}>
      <Async.Pending>
        <UserPlaceholder />
      </Async.Pending>
      <Async.Fulfilled>{data => <UserDetails data={data} />}</Async.Fulfilled>
      <Async.Rejected>{error => <p>{error.message}</p>}</Async.Rejected>
    </Async>
)
export default App;

Let’s go over the functions we declared :

  • loadUser **- We define this function to handle data fetching.** It takes in a prop (userId) and queries the API based on the id.
  • userPlaceholder - This is the fallback component that will be displayed when the promise has not yet resolved. (When the data has not finished loading).
  • *userDetails *- This component handles the actual display of the user data. It takes the data in via props and is only rendered when the promise has been resolved.
  • **Finally,* we utilized the `Async.Pending,Async.Fulfilledand**Async.Rejected\*` functions to simplify our code and make it more readable.

What Next?

In this tutorial, we explored how to use the React-Async library to help us fetch data easily without any hassle. We also looked at some of the helper functions it offers and the philosophy behind it. React-Async is a powerful tool and has the potential to enhance developer experience. To learn more check out the docs here.

I will be writing about React suspense next and how it compares to React Async. They are strongly related and as a matter of fact, React Async shined when suspense was still unborn. That said, both have their differences and you will learn about those in my upcoming post on React suspense.

Like this article? Follow @codebeast on Twitter