In my app I load data from an api, sometimes there is more than one request necessary (i.e. loading the main data and loading the options of a select field).
My idea is to have a global state in a context (an array in this case), where I add an unique string before the api request and when the request has finished and has returned its data, this unique string is removed from the array. As long as the array length is greater than zero, the loading component is shown and if there are two different api requests that take different times, the loading component isn’t removed until the last request has ended.
My problem is, that the loading component mostly is still shown, even if the "stop function" has removed the unique string and the array is empty.
This is my code so far:
ContextProvider (./contexts/LoadingContext.jsx):
'use strict';
import React, {
createContext,
useContext,
useState,
} from 'react';
const LoadingContext = createContext({
startLoading: (id) => {},
stopLoading: (id) => {},
isLoading: false,
});
export const useLoading = () => useContext(LoadingContext);
export const LoadingProvider = ({ children }) => {
const [loading, setLoading] = useState([]);
const startLoading = (id) => {
setLoading([...loading, id]);
};
const stopLoading = (id) => {
const _loading = [...loading];
const index = _loading.indexOf(id);
if(index !== -1) {
_loading.splice(index, 1);
setLoading(_loading);
}
};
return (
<LoadingContext.Provider value={{
startLoading: startLoading,
stopLoading: stopLoading,
isLoading: loading.length > 0,
}}>
{children}
</LoadingContext.Provider>
);
}
GlobalLoadingBackdrop (./GlobalLoadingBackdrop.jsx):
'use strict';
import React from 'react';
import Backdrop from '@mui/material/Backdrop';
import CircularProgress from '@mui/material/CircularProgress';
import { useLoading } from './contexts/LoadingContext';
export const GlobalLoadingBackdrop = () => {
const { isLoading} = useLoading();
return (
<Backdrop
sx={{
color: '#fff',
zIndex: (theme) => theme.zIndex.modal + 1,
}}
open={isLoading}
>
<CircularProgress color={'inherit'} />
</Backdrop>
);
}
Usage somewhere in a functional component:
const { startLoading, stopLoading } = useLoading();
try {
// start loading
startLoading('GET/some/api/request');
// call api
const response = await axios.get('/some/api/request');
// check response
if(response.hasOwnProperty('error') && response.error !== null) {
throw new Error(response.errorMessage);
}
// do somthing with the data
} catch (e) {
console.error(e);
} finally {
// stop loading
stopLoading('GET/some/api/request');
}
Any help or idea how to solve the problem will be appreciated…
2
Answers
Dans la fonction stopLoading, utilisez filter pour créer un nouveau tableau sans muter l’original, plutôt que d’utiliser splice.
Déplacez l’appel à stopLoading dans la clause finally de votre bloc try-catch. Cela garantit que la fonction stopLoading est toujours appelée, même en cas d’erreur non gérée pendant la requête API.
I think the error might be caused by the way you are setting and removing the
ids
from theloading
array. You are using the spread operator to create a new array, but you are also mutating the original array withsplice
. This might cause some inconsistency and confusion for React.A better way to update the loading array is to use a functional update, which means passing a function to setLoading that receives the previous array as an argument, and returns a new array based on it. This way, you always use the latest array value. For example:
And I think you can use
useTransition
hook which is newly introduced in React 18 for loading.To learn more, please see documentation.