The Problem: You’re working on a React component that needs to display a loading spinner while multiple API calls are in progress. Your current approach uses a single boolean state variable to control the spinner’s visibility, but this causes the spinner to disappear prematurely when any one of the API calls completes, even if others are still running. You want a cleaner way to manage the loading state so the spinner remains visible until all API calls have finished.
TL;DR: The Quick Fix: Use Promise.allSettled() to wait for all your API calls to complete before hiding the spinner.
Understanding the “Why” (The Root Cause): Your original approach uses a single boolean state variable (showSpinner). When any API call finishes (successfully or with an error), the loading state is set to false, causing the spinner to disappear. This is problematic because other API calls might still be in progress. To keep the spinner showing until all asynchronous operations are complete, you need a mechanism that waits for all promises to resolve (or reject). Promise.allSettled() provides exactly that functionality. It allows you to wait for all promises in an array to either fulfill or reject, regardless of their individual success or failure. This ensures the loading spinner remains visible until every API call has completed its work.
Step-by-Step Guide:
Step 1: Implement Promise.allSettled(): Modify your code to use Promise.allSettled() to wait for all API calls to complete.
const [showSpinner, setShowSpinner] = useState(false);
const fetchAllData = async () => {
setShowSpinner(true);
await Promise.allSettled([
fetchUserData(),
fetchProducts(),
fetchSettings()
]);
setShowSpinner(false);
};
useEffect(() => {
fetchAllData();
}, []); // Empty dependency array ensures this runs only once on mount.
Step 2: Ensure API Calls Return Promises: Verify that your fetchUserData(), fetchProducts(), and fetchSettings() functions all return Promises. If they are using async/await internally, they implicitly return a Promise. If not, you might need to wrap them in a Promise.resolve() or use fetch API directly. For instance:
const fetchUserData = async () => {
//Existing code using await
// ...
}
const fetchProducts = () => { //Example of a non async function
return new Promise((resolve, reject) => {
//Make your API call here and resolve or reject based on outcome
setTimeout(() => {resolve("Products Fetched")}, 2000);
})
}
Step 3: Handle Errors (Optional): While Promise.allSettled() waits for all promises, it doesn’t prevent errors. To handle potential errors, you can access the results from Promise.allSettled():
const fetchAllData = async () => {
setShowSpinner(true);
const results = await Promise.allSettled([
fetchUserData(),
fetchProducts(),
fetchSettings()
]);
results.forEach(result => {
if (result.status === 'rejected') {
console.error('API call failed:', result.reason);
// Handle the error appropriately, e.g., display an error message
}
});
setShowSpinner(false);
};
Common Pitfalls & What to Check Next:
-
Incorrect Promise Handling: Ensure that your API calls correctly return Promises, and handle any errors that may occur. Use try...catch blocks within your individual API functions if necessary for more granular error handling.
-
Unnecessary Re-renders: Ensure that the only thing that is updated in your component when setShowSpinner is invoked is the loading spinner itself. If not, it might cause unnecessary re-renders or unexpected behavior. Consider using React.memo on the component to prevent unneeded re-renders.
-
Concurrent Requests: If you need to manage and handle concurrent API requests effectively, consider employing debouncing or throttling techniques if appropriate to the situation. This can help to avoid unnecessary load on your servers, and improve the overall user experience.
Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!