React | Asynchronous Fetch from API

React | Asynchronous Fetch from API

TL;DR

Ensure to use 'useState()' to store the values retrieved from API, and 'await' or '.then' to make the actual API call with help of async.

If you are new to React then understanding the working of Promise and Async can be a little tricky.

Let's say that we have to load our react component with the data that is fetched from the API, how would we do it ?

I am using react hooks for simplicity purpose, axios to make the API call. The steps would be very simple:

  1. I want to make an API call when the component is loaded, will use 'useEffect()' to make this. Ensure to use [] so that it is called only once.
  2. Once the products are retrieved, loop on each of the items and create an component
  3. Display the component if and only if the products are retrieved.

The code would look like:

import React, {Fragment, useState, useEffect} from "react";
import axios from "axios";
import SingleItem from "../components/SingleItem";

import {getTopProductsForHome} from "../../config/API";

const Home = () => {
    const productList = [];

    useEffect(() => {

            try {
                const result = axios.get(`${getTopProductsForHome}`);
                productList = result.data.data;
            } catch (error) {
                console.log(error);
            }

    }, []);

    let ProductComponent = null;

    if (productList !== null) {
        ProductComponent = Object.entries(productList).map((prodList) => {
            return <SingleItem key={prodList.id} productInfo={prodList} />;
        });
    }

    return (
        <Fragment>
            <div style={{marginTop: 20}} >
                Top Recommended products near you
            </div>
            <div>
                {ProductComponent}
            </div>
        </Fragment>
    );
};

export default Home;

This would not work because axios.get is an asynchronous call and needs to be handled accordingly.

Let's fix that by writing a function "getProdList" inside useEffect and also lets call "getProdList" inside useEffect.

import React, {Fragment, useState, useEffect} from "react";
import axios from "axios";
import SingleItem from "../components/SingleItem";

import {getTopProductsForHome} from "../../config/API";

const Home = () => {
    productList = []

    useEffect(() => {
        const getProdList = async () => {
            try {
                const result = await axios.get(`${getTopProductsForHome}`);
                productList = result.data.data
            } catch (error) {
                console.log(error);
            }
        };
        getProdList();
    }, []);

    let ProductComponent = null;

    if (productList !== null) {
        ProductComponent = Object.entries(productList).map((prodList) => {
            return <SingleItem key={prodList.id} productInfo={prodList} />;
        });
    }

    return (
        <Fragment>
            <div style={{marginTop: 20}} >
                Top Recommended products near you
            </div>
            <div>
                {ProductComponent}
            </div>
        </Fragment>
    );
};

export default Home;

Here, we have taken care of the asynchronous property, but each time we run, we see that ProductComponent will have a promise (when we console.log (ProductComponent) ). This is because of how promises and callback is structured in javascript.

How do we get over this ? We can use useState hook to store the value so that it gets updated once the promise( using the await keyword) is completed.

The final working solution would look like:

import React, {Fragment, useState, useEffect} from "react";
import axios from "axios";
import SingleItem from "../components/SingleItem";

import {getTopProductsForHome} from "../../config/API";

const Home = () => {
    const [productList, setProductList] = useState([]);

    useEffect(() => {
        const getProdList = async () => {
            try {
                const result = await axios.get(`${getTopProductsForHome}`);
                setProductList(result.data.data);
            } catch (error) {
                console.log(error);
            }
        };
        getProdList();
    }, []);

    let ProductComponent = null;

    if (productList !== null) {
        ProductComponent = Object.entries(productList).map((prodList) => {
            return <SingleItem key={prodList.id} productInfo={prodList} />;
        });
    }

    return (
        <Fragment>
            <div style={{marginTop: 20}} >
                Top Recommended products near you
            </div>
            <div>
                {ProductComponent}
            </div>
        </Fragment>
    );
};

export default Home;

Please let me know if this works, and if you guys have a better way to handle this. Will write about other interesting hooks that can be used to fetch products further down the line.

Credits: Photo by Kevin Ku from Pexels