Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

rfmapp's avatar

set Data Table data to be ready only at certain condition

I have a "report render" Component that renders a v-data-table table. The user chooses start and end dates and locations from a form from a previous component and then is redirected to the "report render" page. When the user first access the page, the backend grabs data from the database and everything works fine. But there is a "grab fresh data" button and when the user clicks it, the code grabs selected location ids and makes an axios call for each id (for loop), updating the array of data to be displayed by the table. This response takes a litle more time, since it's calling an api for each location. There is a loading prop set for the user to have a sense that something is being prepared.

My problem: the loading animation finishes right after the first set of data is pushed to the data array to be displayed by the table, but the following data is eventually shown. How could I "tell" the loading prop to wait for the whole loop to be done to consider the loading also done?

Here is the code:

<template>
    <div>
        <div class="section-wrapper">
            <v-data-table id="table" :items="reportData" :loading="loading" :headers="headers">
                <template v-slot:top>
                    <div id="header" class="d-flex px-3 pt-2" color="white">
                        <v-toolbar-title>
                            <img class="nav-logo" :src="'/assets/icon.png'" alt />
                        </v-toolbar-title>

                        <v-spacer></v-spacer>
                        <div class="text-center m-auto">
                            <h5>{{ title }}</h5>
                            <p>{{ fromDate }} - {{ toDate }}</p>
                            <p v-if="dataCount">
                                {{ total }} : <b>{{ dataCount }}</b>
                            </p>
                        </div>
                        <v-spacer></v-spacer>

                        <v-btn class="elevation-0 ml-4" @click="getReport(true)" fab light small color="white">
                            <v-icon color="primary">mdi-sync</v-icon>
                        </v-btn>

                    </div>
                </template>
                <template v-slot:item.StartDate="{ item }">
                    <p class="mb-0">
                        {{ item.StartDate | moment("MM/DD/YYYY") }}
                    </p>
                </template>
                <template v-slot:item.StartTime="{ item }">
                    <p class="mb-0">
                        <!-- {{ item.StartDate | moment("h:mm a") }} -->
                        {{
                moment
                    .tz(
                        item.StartTime,
                        user.timezone ? user.timezone : default_timezon
                    )
                    .format("h:mm a")
            }}
                        ({{
                    $constants.timezonAbbreviations(
                        user.timezone ? user.timezone : default_timezon
                    )
                }})
                    </p>
                </template>
            </v-data-table>
        </div>
    </div>
</template>

<script>
import "jspdf-autotable";
import moment from "moment";
import { mapState } from "vuex";
import axios from "axios";

export default {
    props: [
        "from_date",
        "to_date",
        "selectedLocations",
    ],
    data: () => ({
        default_timezon: "America/Chicago",
        title: "Report",
        total: " Total Count",
        reportData: [],
        tempData: [],
        fromDate: null,
        toDate: null,
        loading: false,
        moment: moment,
        dataCount: 0,
        headers: [
            {text: "ID", value: "L_ID", sortable: false},
            {text: "Location", value: "LocationName", sortable: false},
            {text: "Events Count", value: "EventsCount", sortable: false},
        ],
    }),
    mounted() {
        if (!this.from_date || !this.to_date) {
            this.$router.go(-1);
        } else {
            this.getReport();
            this.fromDate = moment().utc().format("MM/DD/YYYY");
            this.toDate = this.formattedDate(this.to_date);
        }
    },
    computed: {
        
    },
    methods: {
        formattedDate(date) {
            const list = date.split("-");
            return `${list[1]}/${list[2]}/${list[0]}`;
        },
        async getReport(sync_api) {
            if (this.loading == true) return;
            this.loading = true;

            const url = "/api/get_locations_details";
            const data = new FormData();
            
            data.append("from_date", this.from_date);
            data.append("to_date", this.to_date);
            
            if (sync_api == true) {
                data.append("sync_api", 1);
            }
            
            if (this.selectedLocations) {
                try {
                    await this.makeApiRequest(data, url, this.selectedLocations);
                    
                    if (sync_api == true) {
                        this.$store.dispatch(
                            "global/snackBarState",
                            {
                                show: true,
                                color: "primary",
                                message: "Processing, Please wait few Seconds...",
                            },
                            { root: true }
                        );
                    }
                    this.loading = false;
                    this.tempData = [];
                } catch (error) {
                    console.error("ERROR HERE: ", error.message);
                }
            }
        },
        makeApiRequest(data, url, locations) {
            for (const location of locations) {
                data.append("selectedPartners", location);
                axios.post(url, data)
                .then((r) => {
                    this.tempData = this.tempData.concat(r.data.data);
                    this.reportData = this.tempData;
                    this.dataCount = this.reportData.length;
                    
                }).catch((error) => {
                    this.loading = false;
                    console.error("ERROR HERE: ", error.message);
                });
            }
        },
    }
}
</script>
0 likes
3 replies
LaryAI's avatar
Level 58

The issue you're facing is due to the asynchronous nature of the axios.post calls within the for loop. Each API request is sent out, but the loop does not wait for each request to complete before moving on to the next iteration. As a result, the loading state is set to false before all the data has been fetched.

To fix this, you can use Promise.all to wait for all the API requests to complete before setting the loading state to false. Here's how you can modify the makeApiRequest method:

async makeApiRequest(data, url, locations) {
    // Create an array to hold all the promises
    let promises = [];

    for (const location of locations) {
        // Create a new FormData instance for each request to avoid appending to the same instance
        let locationData = new FormData();
        for (let [key, value] of data.entries()) {
            locationData.append(key, value);
        }
        locationData.append("selectedPartners", location);

        // Push the axios promise into the promises array
        promises.push(axios.post(url, locationData));
    }

    try {
        // Wait for all the promises to resolve
        let results = await Promise.all(promises);

        // Concatenate the results
        for (let response of results) {
            this.tempData = this.tempData.concat(response.data.data);
        }

        // Update the reportData and dataCount after all requests are done
        this.reportData = this.tempData;
        this.dataCount = this.reportData.length;
    } catch (error) {
        console.error("ERROR HERE: ", error.message);
    } finally {
        // Set loading to false only after all requests have completed
        this.loading = false;
    }
}

And in the getReport method, remove the this.loading = false; line since it will now be handled in the finally block of the makeApiRequest method:

async getReport(sync_api) {
    if (this.loading == true) return;
    this.loading = true;

    const url = "/api/get_locations_details";
    const data = new FormData();
    
    data.append("from_date", this.from_date);
    data.append("to_date", this.to_date);
    
    if (sync_api == true) {
        data.append("sync_api", 1);
    }
    
    if (this.selectedLocations) {
        try {
            await this.makeApiRequest(data, url, this.selectedLocations);
            
            if (sync_api == true) {
                this.$store.dispatch(
                    "global/snackBarState",
                    {
                        show: true,
                        color: "primary",
                        message: "Processing, Please wait few Seconds...",
                    },
                    { root: true }
                );
            }
            // this.loading = false; // Remove this line
            this.tempData = [];
        } catch (error) {
            console.error("ERROR HERE: ", error.message);
        }
    }
}

With these changes, the loading state will only be set to false after all the API requests have been completed, ensuring that the loading animation only finishes when the entire data set is ready.

rfmapp's avatar

@LaryAI I forgot to mention: I need this data to be displayed dynamically. So, the table should display the data for each location as they come: display the first location data, then update to ALSO display the second location data, not wait for the whole data set to be ready.

rfmapp's avatar
rfmapp
OP
Best Answer
Level 1

I figure it out. It works with my own code. I just added a counter variable and set it for the selected number of locations, then after each request I decrease it by one and check if its equal to 0. Only then I set the loading prop to false. Here the change (my own code):

async makeApiRequest(data, url, locations) {
            this.responseCount = locations.length;

            for (const location of locations) {
                data.append("locations", location);
                axios.post(url, data)
                .then((r) => {
                    this.tempData = this.tempData.concat(r.data.data);
                    this.reportData = this.tempData;
                    this.dataCount = this.reportData.length;
                    this.responseCount -= 1;
                    if (this.responseCount == 0) {
                        this.loading = false;
                    }
                }).catch((error) => {
                    this.loading = false;
                    console.error("ERROR HERE: ", error.message);
                });
            }
        }

Please or to participate in this conversation.