React | Monitor multiple loading status on the same page

ishi_hisashi

Hisashi Ishihara

Posted on August 22, 2024

React | Monitor multiple loading status on the same page

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;

Loading screen overlays entire page until all the fetch functions are done

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>
Enter fullscreen mode Exit fullscreen mode
  • 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)
  ...}
Enter fullscreen mode Exit fullscreen mode
  • 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]);
  ...
Enter fullscreen mode Exit fullscreen mode
  • 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);
Enter fullscreen mode Exit fullscreen mode

#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">
    </>
    )
}
Enter fullscreen mode Exit fullscreen mode

#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 */} 
      </>
  )
}
Enter fullscreen mode Exit fullscreen mode

#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])
Enter fullscreen mode Exit fullscreen mode

This article is based on React 18 and it is announced in React 19 that context.provider will be deprecated in the future version.

💖 💪 🙅 🚩
ishi_hisashi
Hisashi Ishihara

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