score:0

You don't need any redux for this...

import {lazy, Suspense} from 'react'
import {BrowserRouter, Switch, Route} from 'react-router-dom'

import Loader from './path/to/loader'

const Component1 = lazy(() => import('./path/to/component1')
const Component2 = lazy(() => import('./path/to/component2')

const Router = () => {
  <Suspense fallback={<Loader/>}>
    <BrowserRouter>
      <Switch>
        <Route path='/path1' component={Component1}/>
        <Route path='/path2' component={Component2}/>
      </Switch>
    </BrowserRouter>
  </Suspense>
}

If that doesn't work for you then you'll need to create a parent that can fetch all the data required before rendering and passing it down to children, then you can show the loader in that parent.

If you let a global component manage loading every time a fetch call is made, you'll always end up dealing with that flickering or double loading cascading effect.

score:0

here is the project with redux and a single loading

I made a loading reducer and in each component if data need to be fetched ,start-loading is dispatched and a value is pushed into loading array and when data has been fetched, stop-loadig is dispatched and value is popped from array.

const LoadingReducer = (state = initState, action) => {
  const prevState = { ...state };
  switch (action.type) {
    case "START_LOADING":
      prevState.loading.push(true);
      return { ...state, prevState };
    case "STOP_LOADING":
      prevState.loading.pop(true);
      return { ...state, prevState };
    default:
      return state;
  }
};

and in App.js I just get the loading from store and check if it has value or not then decide to show the Loader or not

import { useEffect, useState } from "react";
import { connect } from "react-redux";
import UserRoutes from "./UserRoutes";
import Loader from "./Loader";
import "./styles.css";

const App = (props) => {
  const [loading, setLoading] = useState(0);
  useEffect(() => {
    setLoading(props.loading);
  }, [props.loading]);
  return (
    <div className="App">
      {loading && <Loader />}
      <UserRoutes />
    </div>
  );
};
const mapStateToProps = (state) => {
  return {
    loading: state.LoadingReducer.loading.length > 0 ? true : false
  };
};

export default connect(mapStateToProps, null)(App);

score:3

I don't know how is your app structured, but this will give you an idea. You can add a property to your state to control the pending requests.

const STATE = {
 requests: []
 ...
}

Define the basic actions ( You can add more like request cancelled, etc). You need to know when a request starts and when it ends:

const requestStartedAction = id => ({
  type: REQUEST_STARTED,
  payload: { id } //--> something to identify the request
});

const requestEndedAction = id => ({
  type: REQUEST_ENDED,
  payload: { id }
});

The reducer:

export default function(state = {}, action) {
  switch (action.type) {
    case REQUEST_STARTED:
      return {
        ...state,
        requests: [...state.requests, action.payload.id], //-> add a new request to the list
        loading: true,
      };
    case REQUEST_ENDED:
      const pendingRequests = state.requests.filter(id => id!== action.payload.id); //--> remove request from the list
      return {
        ...state,
        requests: pendingRequests,
        loading: !Boolean(pendingRequests.length), //--> don't hide the loading if there any request pending
       
      };
    
   ...
  }
}

Then, every time you make a request you call requestStartedAction. When the request being finished you can call requestEndedAction. Basic example using fetch:

dispatch(requestStartedAction(ID));
fetch(URL).finally(() => dispatch(requestEndedAction(ID)));

The loading effect will stop if the requests list is empty.


Related Query

More Query from same tag