5

I have an app that needs to feature a dynamic header, loading an external file from a fetch request.

Once a user logs in they are directed to a portal /portal. I have defined a layout as:

export const UserLayout = ({ children }) => <div><Header /><div className="body">{children}</div></div>

This includes a header with includes dynamic links, then allows the route to be passed through so the header is not loaded every page change.

export const UserRoutes = () => {
  return (
    <UserLayout>
      <Switch> 
        <Route path="/portal" exact component={Portal}/>
        <Route path="/settings" exact component={Settings}/>
      </Switch>
    </UserLayout>
  );
}

The header is first loaded after being redirect from /login to /portal. I have setup in redux a global loader, which basically shows a full screen loading animation while fetching data.

The main issue I am having is, how can I load both parts of my application in a single request?

For example, the header loads the ajax content, which shows a redux loading animation, then stops. Then the route loads, which also shows a redux loading animation, then stops. This creates a flickering effect.

What is the best way to do this to have both the main part load and the child load at the same time?

2
  • 3
    It's hard to determine without code. Can you create codesandbox with fake API requests or at least include more code in your post with whole lifecycle how loading animation is triggered? How do you trigger a loading animation from REDUX? Is it triggered by boolean value changes? Commented Jul 30, 2021 at 16:30
  • does your route data depends on the data fetched by the header? or they are two separate things? and add more code about your api calls and redux setup that would be helpful to provide you with a proper snippet of code Commented Jul 31, 2021 at 12:36

3 Answers 3

4
+100

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.

Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

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);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.