Search code examples
reactjsaxiosuse-effect

How to check data loading in useEffect


I am having a weird issue inside useEffect() in my React component. I have to make 2 separate axios requests to get data when the page loads. I am trying to use a hook variable to see if the data objects are populated before passing them to the JSX. Here's my current configuration:

import React, { useState, useEffect } from 'react';
import Navbar from '../components/layout/Navbar';
import ContactsCard from '../components/layout/ContactsCard';
import EmailCard from '../components/layout/EmailCard';
import MeetingsCard from '../components/layout/MeetingsCard';
import { useParams } from "react-router-dom";
import config from './../config/config';
import axios from "axios";

function SummaryPageNew() {
    let { selectName } = useParams();
    const [contactData, setContactData] = useState();
    const [meetingData, setMeetingData] = useState();
    const [loadingData, setLoadingData] = useState(true);

    //API calls
    async function getContactData() {
        axios
        .get(config.apiURL + `/affiliations/name/${selectName}`)
        .then((response) => {
            return setContactData(response.data[0]);

        });
    }

    async function getMeetingData() {
        axios
        .get(config.apiURL + `/meetings_attendees/name/${selectName}`)
        .then((response) => {
            return setMeetingData(response.data);
        });
    }

    useEffect((loadingData) => {
        getContactData();
        getMeetingData();
        setLoadingData(false);

        if (loadingData) {
            //if the result is not ready so you make the axios call
            getContactData();
            getMeetingData();
            setLoadingData(false);
        }
    }, []); // eslint-disable-line react-hooks/exhaustive-deps

    return (
        <div>
            <Navbar />
            <div>
                <div style={{ textAlign: "center" }}>
                    <h3>Contact Information</h3>
                    <h5>Profile: {selectName}</h5>
                </div>
                {loadingData ? (
                    <p>Loading Please wait...</p>
                ) : (
                    <div className="row">
                        <ContactsCard contactData={contactData} />
                        <EmailCard emailData={meetingData} />
                        <MeetingsCard meetingData={meetingData} />
                    </div>
                )}
            </div>
        </div>
    )
}

export default SummaryPageNew

I have tried moving the setLoadingData(false) method inside the axios calls. If I move it inside the getMeetingData() call. This works ... sometimes. Apparently, on some occasions, it loads first and then the contactData doesn't get returned. In the current configuration, the DOM renders with "Loading Please wait...". What am I doing wrong here? How can I resolve this issue?


Solution

  • There are many issues with your code.

    1. useEffect functions don't take any parameters. Your declaration of loadingData as a parameter is covering the actual loadingData variable in your component, and React will not pass a value for this.

    2. You're missing a dependency on loadingData in your call to useEffect. As is, the function will only execute once and then never again as long as the component stays mounted. So, loadingData never gets set to false. Generally, it is a bad idea to avoid warnings about useEffect dependencies unless you have a very good reason.

    My recommended solution would be to avoid storing extra state for the "loading" status. Instead, I would just check whether the two state values have been populated yet, and show the "Loading..." text if either is not.

    This leaves you with:

    function SummaryPageNew() {
        let { selectName } = useParams();
        const [contactData, setContactData] = useState();
        const [meetingData, setMeetingData] = useState();
        const isReady = contactData !== undefined && meetingData !== undefined;
    
        //API calls
        async function getContactData() { ... }
    
        async function getMeetingData() { ... }
    
        useEffect((loadingData) => {
            getContactData();
            getMeetingData();
        }, []);
    
        return (
            <div>
                <Navbar />
                <div>
                    <div style={{ textAlign: "center" }}>
                        <h3>Contact Information</h3>
                        <h5>Profile: {selectName}</h5>
                    </div>
                    {isReady ? (
                        <div className="row">
                            <ContactsCard contactData={contactData} />
                            <EmailCard emailData={meetingData} />
                            <MeetingsCard meetingData={meetingData} />
                        </div>
                    ) : (
                        <p>Loading Please wait...</p>
                    )}
                </div>
            </div>
        )
    }
    

    react-query is a very powerful library for fetching data asynchronously using hooks. This avoids having to manage complex state which can easily fall out of sync. However, I'd learn the fundamentals of react hooks first!