ytpm
ytpm

Reputation: 5150

Show loading screen globally in React.js while calling it from different pages

I was trying to implement a global loading screen when fetching data from an API, I read this answer and tried to implemented something similar.

loading.provider.js

import { createContext, useContext, useState } from "react";

const LoadingContext = createContext({
  loading: false,
  setLoading: null
});

export function LoadingProvider({ children }) {
  const [loading, setLoading] = useState(false);
  const value = { loading, setLoading };
  return (
    <LoadingContext.Provider value={value}>{children}</LoadingContext.Provider>
  );
};

export function useLoading() {
  const context = useContext(LoadingContext);
  if (!context) {
    throw new Error('useLoading must be used within LoadingProvider');
  }

  return context;
};

app.js

The LoadingScreen has been added to this screen:

import Layout from '@/components/Layout/Layout';
import { LoadingProvider, useLoading } from '@/Providers/loading.provider';
import LoadingScreen from "./LoadingScreen";

export default function App({ Component, pageProps }) {
  const { loading } = useLoading();

  return (
    <>
      <CssBaseline />
      <LoadingProvider>
        <AppStateProvider>
          { loading && <LoadingScreen loading={true} bgColor='#fff' spinnerColor={'#00A1FF'} textColor='#676767'></LoadingScreen> }
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </AppStateProvider>
      </LoadingProvider>
    </>
  );
};

I have a Layout component which holds a SideMenu, top AppBar and the main components:

Layout.js

export default function Layout({ children }) {
  return (
    <Fragment>
      <Box sx={{ display: 'flex' }}>
        <AppBar />
        <SideMenu />
        <Box component="main" sx={mainContentWrapper}>
          <div className={styles.container}>
            <main className={styles.main}>
              {children}
            </main>
          </div>
        </Box>
      </Box>
    </Fragment>
  );
}

Now I was trying to show it from a page ([identifier].js):

import { useLoading } from "@/Providers/loading.provider";

export default function MainPage({ response }) {
  const { loading, setLoading } = useLoading();
  const btnClickedAll = async (data) => {
    setLoading(true);
  };

  return (
    <Fragment>
      <Button sx={{ ml: 2 }} key={'btnAll'} onClick={btnClickedAll} variant="contained">All</Button>,
    </Fragment>
  );
}

loading is being called after setting it with setLoading(true) but the state is not changing inside app.js. Can someone help me and point me to the right direction?


Edited: Made some changes in code according to @DustInComp suggestion.

Upvotes: 1

Views: 2143

Answers (1)

G Sriram
G Sriram

Reputation: 555

The reason is that your LoadingProvider is inside of your app.js, and only children inside off the LoadingProvider would have access to the loading state.

Option 1

What you could do is to move wrap the entire app.js in LoadingProvider in the parent, maybe index.js.

In index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import './index.css';
import './bootstrap.min.css';
import App from './App';

ReactDOM.render(
    <LoadingProvider>
        <App />
    </LoadingProvider>,
    document.getElementById('root')
);

OR

Option 2

You could create a child component of everything inside of app.js.

In Child.js (Name it as you please)

const Child = ({ Component, pageProps }) => {
    const { loading } = useLoading();
     <AppStateProvider>
          { loading && <LoadingScreen loading={true} bgColor='#fff' spinnerColor={'#00A1FF'} textColor='#676767'></LoadingScreen> }
          <Layout>
            <Component {...pageProps} />
          </Layout>
     </AppStateProvider>
};

export default Child;

And in app.js

export default function App({ Component, pageProps }) {
  return (
    <>
      <CssBaseline />
      <LoadingProvider>
        <Child Component={Component} pageProps={pageProps}/>
      </LoadingProvider>
    </>
  );
};

OR

Option 3

You could create a loader component of the loader and import it into app.js.

In Loader.js (Name it as you please)

const Loader = () => {
    const { loading } = useLoading();
    return loading && <LoadingScreen loading={true} bgColor='#fff' spinnerColor={'#00A1FF'} textColor='#676767'></LoadingScreen>;        
};

export default Loader;

And in app.js

export default function App({ Component, pageProps }) {
  return (
    <>
      <CssBaseline />
      <LoadingProvider>
        <AppStateProvider>
          <Loader />
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </AppStateProvider>
      </LoadingProvider>
    </>
  );
};

Upvotes: 1

Related Questions