Hisashi Ishihara
Posted on August 22, 2024
Introduction
When developing a data-driven app, we often need to create a dashboard page composed of multiple data sources. For instance, in a finance app, charts displaying monthly sales trends, expenses, and cash balances are called as separate components on the same page.
In such cases, you might want to display a "Loading" UI for the entire page until all the data has been fully loaded...just like this;
There could be several approaches to achieve this, but I'd like to introduce a method that leverages the Context API.
Thinking process
- If specific components are only used on specific pages, the issue can seemingly be solved simply by managing state alongside those pages.
- However, in this case, the components containing the fetch functions are reused across multiple screens. Therefore, it’s preferable to have a mechanism where each page autonomously detects and monitors the fetch progress according to the number of components present.
Solution
Concept
- Leverage the Context API. Establish a state that detects the number of loading processes and monitors them until completion, wrapping the entire app with this state.
- The general structure is as follows: The LAYOUT layer defines the UI according to the loading status, while each COMPONENT layer reports its loading status via the Context API. The overall app then monitors these statuses. Nothing specific is assigned to the PAGE layer, meaning that there is no page-specific management like "Page X has Y number of fetch functions."
<APP>
<LAYOUT>
<PAGE>
<COMPONENT>
- After setting up the Context API (following steps #1-3 below), all you need to do is execute startLoading and stopLoading within the fetch functions, with no additional setup required, allowing the app to scale while being easy to maintain. I believe this is a highly scalable and simple approach for maintenance.
Step
#1. Set up context API
- Call the context variable and states to count loadings;
// Create the context
const LoadingContext = createContext();
// Create a provider component
export const LoadingProvider = ({ children }) => {
const [loading, setLoading] = useState(true);
const [loadingCount, setLoadingCount] = useState(0)
...}
- Then, make some simple functions to watch the progress of loadings
...
const startLoading = useCallback(() => {
setLoadingCount((count) => count + 1);
}, []);
const stopLoading = useCallback(() => {
setLoadingCount((count) => count - 1);
}, []);
useEffect(() => {
setLoading(loadingCount > 0); // if all the loadings is completed (loadingCount=0), 'loading' gets false.
}, [loadingCount]);
...
- Return the provider and pass the states and functions
...
const value = useMemo(
() => ({loading, startLoading, stopLoading,}),
[loading, startLoading, stopLoading]);
return (
<LoadingContext.Provider value={value}>{children}</LoadingContext.Provider>
);};
// Custom hook to use the LoadingContext
export const useLoading = () => useContext(LoadingContext);
#2. At the App.jsx, wrap the entire application or specific page
const App = ()=>{
return (
<LoadingProvider>
<Layout>
<Main/> // example
<Sub/> // example
</Layout>
</LoadingProvider>
)
}
const Main = ()=>{
return(
<>
<ChartComponent type="A">
<ChartComponent type="B">
<ChartComponent type="C">
</>
)
}
const Sub =()=>{
return(
<>
<ChartComponent type="A">
<ChartComponent type="C">
</>
)
}
#3. Set UI effect at the Layout.jsx
import { useLoading } from "YOUR FILE PATH";
const Layout = ({children}) => {
const { loading } = useLoading();
const [showLoadingScreen, setShowLoadingScreen] = useState(true);
const handleCloseLoadingScreen = () => {
setShowLoadingScreen(false);
};
return (
<>
{loading && showLoadingScreen && (
<LoadingScreen onClose={handleCloseLoadingScreen} />)}
</>
)
}
const LoadingScreen = ({onClose})=>{
{/* FOR ESC KEYDOWN */}
useEffect(() => {
const handleEsc = (event) => {
if (event.key === "Escape") {
onClose();
}};
window.addEventListener("keydown", handleEsc);
return () => {
window.removeEventListener("keydown", handleEsc);
};}, [onClose]);
return(
<>
{/* ADD LOADING DISPLAY */}
</>
)
}
#4. At the component where HTTP GET request occurs
...
import { useLoading } from "YOUR FILE PATH";
const ChartComponent =({type})=>{
const { startLoading, stopLoading } = useLoading();
useEffect(() => {
startLoading();
//...
//YOUR FETCH FUNCTION
//...
stopLoading();
}
,[startLoading, stopLoading])
This article is based on React 18 and it is announced in React 19 that context.provider will be deprecated in the future version.
Posted on August 22, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 12, 2024