ChatGPT解决这个技术问题 Extra ChatGPT

React - Display loading screen while DOM is rendering?

This is an example from Google Adsense application page. The loading screen displayed before the main page showed after.

https://i.stack.imgur.com/ngk1r.gif

I don't know how to do the same thing with React because if I make loading screen rendered by React component, it doesn't display while page is loading because it has to wait for DOM rendered before.

Updated:

I made an example of my approach by putting screen loader in index.html and remove it in React componentDidMount() lifecycle method.

Example and react-loading-screen.

Show what you want to show in plain js, then make it hidden or remove from DOM when react has mounted. All you need to do is to hide it from react code.
This is simply wonderful! Thank you.
Agree this is a wonderful approach. I've shipped several react apps where I put the loading screen inside of
(which works) but there can be a short-lived "white screen" between the first call of ReactDOM.render() and when when the component would actually paint. Using the fixed positioning for the loading screen and then componentDidUpdate (or useEffect hook) with CSS to fade then fully remove it is wonderful. It ensures you arent removing the loading screen until your fully painted react component is already underneath, ready to be viewed.

O
Ori Drori

The goal

When the html page is rendered, display a spinner immediately (while React loads), and hide it after React is ready.

Since the spinner is rendered in pure HTML/CSS (outside of the React domain), React shouldn't control the showing/hiding process directly, and the implementation should be transparent to React.

Solution 1 - the :empty pseudo-class

Since you render react into a DOM container - <div id="app"></div>, you can add a spinner to that container, and when react will load and render, the spinner will disappear.

You can't add a DOM element (a div for example) inside the react root, since React will replace the contents of the container as soon as ReactDOM.render() is called. Even if you render null, the content would still be replaced by a comment - <!-- react-empty: 1 -->. This means that if you want to display the loader while the main component mounts, data is loading, but nothing is actually rendered, a loader markup placed inside the container (<div id="app"><div class="loader"></div></div> for example) would not work.

A workaround is to add the spinner class to the react container, and use the :empty pseudo class. The spinner will be visible, as long as nothing is rendered into the container (comments don't count). As soon as react renders something other than comment, the loader will disappear.

Example 1

In the example you can see a component that renders null until it's ready. The container is the loader as well - <div id="app" class="app"></div>, and the loader's class will only work if it's :empty (see comments in code):

class App extends React.Component { state = { loading: true }; componentDidMount() { // this simulates an async action, after which the component will render the content demoAsyncCall().then(() => this.setState({ loading: false })); } render() { const { loading } = this.state; if(loading) { // if your component doesn't have to wait for an async action, remove this block return null; // render null when app is not ready } return (

I'm the app
); } } function demoAsyncCall() { return new Promise((resolve) => setTimeout(() => resolve(), 2500)); } ReactDOM.render( , document.getElementById('app') ); .loader:empty { position: absolute; top: calc(50% - 4em); left: calc(50% - 4em); width: 6em; height: 6em; border: 1.1em solid rgba(0, 0, 0, 0.2); border-left: 1.1em solid #000000; border-radius: 50%; animation: load8 1.1s infinite linear; } @keyframes load8 { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }

Example 2

A variation on using the :empty pseudo class to show/hide a selector, is setting the spinner as a sibling element to the app container, and showing it as long as the container is empty using the adjacent sibling combinator (+):

class App extends React.Component { state = { loading: true }; componentDidMount() { // this simulates an async action, after which the component will render the content demoAsyncCall().then(() => this.setState({ loading: false })); } render() { const { loading } = this.state; if(loading) { // if your component doesn't have to wait for async data, remove this block return null; // render null when app is not ready } return (

I'm the app
); } } function demoAsyncCall() { return new Promise((resolve) => setTimeout(() => resolve(), 2500)); } ReactDOM.render( , document.getElementById('app') ); #app:not(:empty) + .sk-cube-grid { display: none; } .sk-cube-grid { width: 40px; height: 40px; margin: 100px auto; } .sk-cube-grid .sk-cube { width: 33%; height: 33%; background-color: #333; float: left; animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; } .sk-cube-grid .sk-cube1 { animation-delay: 0.2s; } .sk-cube-grid .sk-cube2 { animation-delay: 0.3s; } .sk-cube-grid .sk-cube3 { animation-delay: 0.4s; } .sk-cube-grid .sk-cube4 { animation-delay: 0.1s; } .sk-cube-grid .sk-cube5 { animation-delay: 0.2s; } .sk-cube-grid .sk-cube6 { animation-delay: 0.3s; } .sk-cube-grid .sk-cube7 { animation-delay: 0s; } .sk-cube-grid .sk-cube8 { animation-delay: 0.1s; } .sk-cube-grid .sk-cube9 { animation-delay: 0.2s; } @keyframes sk-cubeGridScaleDelay { 0%, 70%, 100% { transform: scale3D(1, 1, 1); } 35% { transform: scale3D(0, 0, 1); } }

Solution 2 - Pass spinner "handlers" as props

To have a more fine grained control over the spinners display state, create two functions showSpinner and hideSpinner, and pass them to the root container via props. The functions can manipulate the DOM, or do whatever needed to control the spinner. In this way, React is not aware of the "outside world", nor needs to control the DOM directly. You can easily replace the functions for testing, or if you need to change the logic, and you can pass them to other components in the React tree.

Example 1

const loader = document.querySelector('.loader'); // if you want to show the loader when React loads data again const showLoader = () => loader.classList.remove('loader--hide'); const hideLoader = () => loader.classList.add('loader--hide'); class App extends React.Component { componentDidMount() { this.props.hideLoader(); } render() { return (

I'm the app
); } } // the setTimeout simulates the time it takes react to load, and is not part of the solution setTimeout(() => // the show/hide functions are passed as props ReactDOM.render( , document.getElementById('app') ) , 1000); .loader { position: absolute; top: calc(50% - 4em); left: calc(50% - 4em); width: 6em; height: 6em; border: 1.1em solid rgba(0, 0, 0, 0.2); border-left: 1.1em solid #000000; border-radius: 50%; animation: load8 1.1s infinite linear; transition: opacity 0.3s; } .loader--hide { opacity: 0; } @keyframes load8 { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }

Example 2 - hooks

This example uses the useEffect hook to hide the spinner after the component mounts.

const { useEffect } = React; const loader = document.querySelector('.loader'); // if you want to show the loader when React loads data again const showLoader = () => loader.classList.remove('loader--hide'); const hideLoader = () => loader.classList.add('loader--hide'); const App = ({ hideLoader }) => { useEffect(hideLoader, []); return (

I'm the app
); } // the setTimeout simulates the time it takes react to load, and is not part of the solution setTimeout(() => // the show/hide functions are passed as props ReactDOM.render( , document.getElementById('app') ) , 1000); .loader { position: absolute; top: calc(50% - 4em); left: calc(50% - 4em); width: 6em; height: 6em; border: 1.1em solid rgba(0, 0, 0, 0.2); border-left: 1.1em solid #000000; border-radius: 50%; animation: load8 1.1s infinite linear; transition: opacity 0.3s; } .loader--hide { opacity: 0; } @keyframes load8 { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }


Could you clarify where the last 2 code sections should be? The first is clearly in the javascript src file for the react component, the third I'm guessing goes in the html template to be rendered by said js file, but where does the second go?
The 2nd is the CSS. I've used global CSS, but you can use CSS Modules or CSS in JS. The 3rd is the HTML file, which might include spinner markup if needed (2nd example).
@dryleaf - the setTimeout is not part of the solution. It simulates waiting for an async action before rendering the content.
@hamza-jutt - you should open a new question about that.
return null adds a comment, which will be rendered as a blank screen. :empty pseudo class works along with return null since it ignores comments while determining if the container is empty.
y
yousoumar

This could be done by placing the loading icon in your html file (index.html for example), so that users see the icon immediately after the html file has been loaded.

When your app finishes loading, you could simply remove that loading icon in a lifecycle hook, I usually do that in componentDidMount.


If you mount root component to that icon's parent node, there's even no need to remove it manually. React will clean children of mount node and put its own newly rendered DOM there instead.
I do not put the icon inside the root node of React app, it just does not feel right to me
is there any downside to this for PWAs? will it interfere with the default splash screen?
@benmneb did it interfere ?
@discover Open your chrome developer tools and in the Network section, Apply throttle to mimic slow loading of your application
y
yousoumar

The workaround for this is doing in your render function something like this:

constructor() {
    this.state = { isLoading: true }
}

componentDidMount() {
    this.setState({isLoading: false})
}

render() {
    return(
        this.state.isLoading ? *showLoadingScreen* : *yourPage()*
    )
}

Initialize isLoading as true in the constructor and false on componentDidMount.


When we have called to ajax method to load data to the child components. componentDidMount called before child component data populating. How we overcome this sort of issue ?
For Mounting Life cyle it's fine, would you like to add something for updation life cycle?
do I have to do this in all pages or just in the app entry
c
cbdeveloper

This will happen before ReactDOM.render() takes control of the root <div>. I.e. your App will not have been mounted up to that point.

So you can add your loader in your index.html file inside the root <div>. And that will be visible on the screen until React takes over.

You can use whatever loader element works best for you (svg with animation for example).

You don't need to remove it on any lifecycle method. React will replace any children of its root <div> with your rendered <App/>, as we can see in the GIF below.

Example on CodeSandbox

https://i.stack.imgur.com/Ck0M5.gif

index.html

<head>
  <style>
    .svgLoader {
      animation: spin 0.5s linear infinite;
      margin: auto;
    }
    .divLoader {
      width: 100vw;
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
  </style>
</head>

<body>
  <div id="root">
    <div class="divLoader">
      <svg class="svgLoader" viewBox="0 0 1024 1024" width="10em" height="10em">
        <path fill="lightblue"
          d="PATH FOR THE LOADER ICON"
        />
      </svg>
    </div>
  </div>
</body>

index.js

Using debugger to inspect the page before ReactDOM.render() runs.

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

debugger; // TO INSPECT THE PAGE BEFORE 1ST RENDER

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

a beautiful and elegant solution
C
Carson Evans

If anyone looking for a drop-in, zero-config and zero-dependencies library for the above use-case, try pace.js (https://codebyzach.github.io/pace/docs/).

It automatically hooks to events (ajax, readyState, history pushstate, js event loop etc) and show a customizable loader.

Worked well with our react/relay projects (handles navigation changes using react-router, relay requests) (Not affliated; had used pace.js for our projects and it worked great)


Hey! Can you tell me how to use it with react?
just attach the script to public/index.html and choose a style. this is dead simple, amazing plugin. thank you.
I wouldn't have found pace without this answer. It was so easy to include, and with a little CSS magic and some event attachments I was able to block/disable the app during transitions and customize the spinner.
A
Artem Bernatskyi

When your React app is massive, it really takes time for it to get up and running after the page has been loaded. Say, you mount your React part of the app to #app. Usually, this element in your index.html is simply an empty div:

<div id="app"></div>

What you can do instead is put some styling and a bunch of images there to make it look better between page load and initial React app rendering to DOM:

<div id="app">
  <div class="logo">
    <img src="/my/cool/examplelogo.svg" />
  </div>
  <div class="preload-title">
    Hold on, it's loading!
  </div>
</div>

After the page loads, user will immediately see the original content of index.html. Shortly after, when React is ready to mount the whole hierarchy of rendered components to this DOM node, user will see the actual app.

Note class, not className. It's because you need to put this into your html file.

If you use SSR, things are less complicated because the user will actually see the real app right after the page loads.


This works also I have two places where the loading happens. One is the massive app. and next is the preparation (mounting of various components.) So I get a flashing step because the app.render takes over and the animation gets reset (replaced really.) Would there be a way to avoid that flash? Will React compare the DOM one to one? But from what I understand React adds all sorts of private data in the tags...
B
Badal Saibo

Nowadays we can use hooks as well in React 16.8:

import React, { useState, useEffect } from 'react';

const App = () => {
  const [ spinner, setSpinner ] = useState(true);

  // It will be executed before rendering

  useEffect(() => {
    setTimeout(() => setSpinner(false), 1000)
  }, []);

  // [] means like componentDidMount

  return !spinner && <div>Your content</div>;
};

export default App;

You are missing the point, there is no react until bundle.js is loaded. Html loads before any scripts, hence a loading page should be displayed.
F
Felipe Chernicharo

Just add content inside the

tag and you should be good to go!

// Example:

<div id="root">
   <div id="pre-loader">
        <p>Loading Website...</p>
        <img src="/images/my-loader.gif" />
   </div>
</div>

Once the <App /> is loaded, React will automatically ignore all the content inside the <div id="root"> tag, overwriting it with your actual app!


Yes, It is correctly. Already React will change #root inside
k
kind user

I had to deal with that problem recently and came up with a solution, which works just fine for me. However, I've tried @Ori Drori solution above and unfortunately it didn't work just right (had some delays + I don't like the usage of setTimeout function there).

This is what I came up with:

index.html file

Inside head tag - styles for the indicator:

<style media="screen" type="text/css">

.loading {
  -webkit-animation: sk-scaleout 1.0s infinite ease-in-out;
  animation: sk-scaleout 1.0s infinite ease-in-out;
  background-color: black;
  border-radius: 100%;
  height: 6em;
  width: 6em;
}

.container {
  align-items: center;
  background-color: white;
  display: flex;
  height: 100vh;
  justify-content: center;
  width: 100vw;
}

@keyframes sk-scaleout {
  0% {
    -webkit-transform: scale(0);
    transform: scale(0);
  }
  100% {
    -webkit-transform: scale(1.0);
    opacity: 0;
    transform: scale(1.0);
  }
}

</style>

Now the body tag:

<div id="spinner" class="container">
  <div class="loading"></div>
</div>

<div id="app"></div>

And then comes a very simple logic, inside app.js file (in the render function):

const spinner = document.getElementById('spinner');

if (spinner && !spinner.hasAttribute('hidden')) {
  spinner.setAttribute('hidden', 'true');
}

How does it work?

When the first component (in my app it's app.js aswell in most cases) mounts correctly, the spinner is being hidden with applying hidden attribute to it.

What's more important to add - !spinner.hasAttribute('hidden') condition prevents to add hidden attribute to the spinner with every component mount, so actually it will be added only one time, when whole app loads.


A
Alex Zavrazhniy

I'm using react-progress-2 npm package, which is zero-dependency and works great in ReactJS.

https://github.com/milworm/react-progress-2

Installation:

npm install react-progress-2

Include react-progress-2/main.css to your project.

import "node_modules/react-progress-2/main.css";

Include react-progress-2 and put it somewhere in the top-component, for example:

import React from "react";
import Progress from "react-progress-2";

var Layout = React.createClass({
render: function() {
    return (
        <div className="layout">
            <Progress.Component/>
                {/* other components go here*/}
            </div>
        );
    }
});

Now, whenever you need to show an indicator, just call Progress.show(), for example:

loadFeed: function() {
    Progress.show();
    // do your ajax thing.
},

onLoadFeedCallback: function() {
    Progress.hide();
    // render feed.
}

Please note, that show and hide calls are stacked, so after n-consecutive show calls, you need to do n hide calls to hide an indicator or you can use Progress.hideAll().


R
Rich Costello

Setting the timeout in componentDidMount works but in my application I received a memory leak warning. Try something like this.

constructor(props) {
    super(props)
    this.state = { 
      loading: true,
    }
  }
  componentDidMount() {
    this.timerHandle = setTimeout(() => this.setState({ loading: false }), 3500); 
  }

  componentWillUnmount(){
    if (this.timerHandle) {
      clearTimeout(this.timerHandle);
      this.timerHandle = 0;
    }
  }

U
Uzair_07

I don't know if it's too late to answer as you might have probably found the solution, but here's one from my side for future comers, as this question is really a useful one. :
I took a lecture from scrimba.com and here, the teacher started from the classes and then took to hooks. He taught API call through classes and state and everything. Here's his code:

import React, {Component} from "react"

class App extends Component {
    constructor() {
        super()
        this.state = {
            loading: false,
            character: {}
        }
    }
    
    componentDidMount() {
        this.setState({loading: true})
        fetch("https://swapi.dev/api/people/1/")
            .then(response => response.json())
            .then(data => {
                this.setState({
                    loading: false,
                    character: data
                })
            })
    }
    
    render() {
        const text = this.state.loading ? "loading..." : this.state.character.name
        return (
            <div>
                <p>{text}</p>
            </div>
        )
    }
}

export default App

So, it's pretty straight forward, set the loading state to true at the start and keep it so until the data is received, then when it is received, changes the state and sets loading to false and displays the content. Now I tried it with hooks, as a practice and it worked pretty smoothly! A simple yet effective solution. Here's my code:

import React, {useState,useEffect} from 'react'

function App()
{
    const [response, setResponse] = useState([]);
    
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetchResponse() ;
    } , []);

    const fetchResponse = async () => {
        const data = await fetch("https://swapi.dev/api/people/1/");
        const response = await data.json();

        setResponse(response);
        console.log(response.name);
        setLoading(false);
    } 

        const content = loading ? <i className="fas fa-atom fa-spin"></i> : <h1>{response.name}</h1>

    return(
        <section id="w-d-p">
            {content}
        </section>
    )
}

export default App;

So, same logic with hooks. And I get the beautiful spinner while the data is being loaded and then, my data!

Oh and by the way, you can put your own API in the fetch if you dont like this one XD.


V
Victor Trusov

If you're using react-router to manage routes of your app, you can easily add loading screen with react-router-loading library that I made.

It also affects the page switching, but I think if you want to preload the first page, it's natural to preload other pages as well.

https://i.stack.imgur.com/68j5s.gif

The difference between this method and Suspense is that with this library you can continue loading while you're fetching data and so on. Basically this method is very similar to using isLoading state inside a component, but much easier to implement if you have a lot of different pages.

Usage

In your router section import Switch and Route from react-router-loading instead of react-router-dom

import { Switch, Route } from "react-router-loading";

<Switch>
    <Route path="/page1" component={Page1} />
    <Route path="/page2" component={Page2} />
    ...
</Switch>

Add loading prop to every route that must be loaded before switching

<Switch>
    // data will be loaded before switching
    <Route path="/page1" component={Page1} loading />

    // instant switch as before
    <Route path="/page2" component={Page2} />
    ...
</Switch>

Add loadingContext.done() at the end of your initial loading method in components that mentioned in routes with loading prop (in this case it's Page1)

import { LoadingContext } from "react-router-loading";
const loadingContext = useContext(LoadingContext);

const loading = async () => {
    // loading some data

    // call method to indicate that loading is done and we are ready to switch
    loadingContext.done();
};

You can specify loading screen that would be shown at the first loading of your app

const MyLoadingScreen = () => <div>Loading...</div>

<Switch loadingScreen={MyLoadingScreen}>
...
</Switch>

Very nice thx! But there still is the problem of not having a loading screen before the router is set up.
a
adrian95999

I'm also using React in my app. For requests I'm using axios interceptors, so great way to make loader screen (fullpage as you showed an example) is to add class or id to for example body inside interceptors (here code from official documentation with some custom code):

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
     document.body.classList.add('custom-loader');
     return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
       document.body.classList.remove('custom-loader');
       return response;
  }, function (error) {
    // Do something with response error
    return Promise.reject(error);
  }); 

And then just implement in CSS your loader with pseudo-elements (or add class or id to different element, not body as you like) - you can set color of background to opaque or transparent, etc... Example:

custom-loader:before {
    background: #000000;
    content: "";
    position: fixed;
    ...
}

custom-loader:after {
    background: #000000;
    content: "Loading content...";
    position: fixed;
    color: white;
    ...
}

J
Jean Villarreal

this is my implementation, based on the answers

./public/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <title>React App</title>
  <style>
    .preloader {
      display: flex;
      justify-content: center;
    }

    .rotate {
      animation: rotation 1s infinite linear;
    }

    .loader-hide {
      display: none;
    }

    @keyframes rotation {
      from {
        transform: rotate(0deg);
      }

      to {
        transform: rotate(359deg);
      }
    }
  </style>
</head>

<body>
  <div class="preloader">
    <img src="https://i.imgur.com/kDDFvUp.png" class="rotate" width="100" height="100" />
  </div>
  <div id="root"></div>
</body>

</html>

./src/app.js

import React, { useEffect } from "react";

import "./App.css";

const loader = document.querySelector(".preloader");

const showLoader = () => loader.classList.remove("preloader");
const addClass = () => loader.classList.add("loader-hide");

const App = () => {
  useEffect(() => {
    showLoader();
    addClass();
  }, []);
  return (
    <div style={{ display: "flex", justifyContent: "center" }}>
      <h2>App react</h2>
    </div>
  );
};

export default App;


M
Muhammad Taha Khan

You can easily do that by using lazy loading in react. For that you have to use lazy and suspense from react like that.

import React, { lazy, Suspense } from 'react';

const loadable = (importFunc, { fallback = null } = { fallback: null }) => {
  const LazyComponent = lazy(importFunc);

  return props => (
    <Suspense fallback={fallback}>
      <LazyComponent {...props} />
    </Suspense>
  );
};

export default loadable;

After that export your components like this.

export const TeacherTable = loadable(() =>
  import ('./MainTables/TeacherTable'), {
    fallback: <Loading />,
  });

And then in your routes file use it like this.

 <Route exact path="/app/view/teachers" component={TeacherTable} />

Thats it now you are good to go everytime your DOM is rendering your Loading compnent will be displayed as we have specified in the fallback property above. Just make sure that you do any ajax request only in componentDidMount()


Do you have a GitHub sample of such a solution?
M
Magnus Melwin

Edit your index.html file location in the public folder. Copy your image to same location as index.html in public folder. And then replace the part of the contents of index.html containing <div id="root"> </div> tags to the below given html code.

<div id="root">  <img src="logo-dark300w.png" alt="Spideren" style="vertical-align: middle; position: absolute;
   top: 50%;
   left: 50%;
   margin-top: -100px; /* Half the height */
   margin-left: -250px; /* Half the width */" />  </div>

Logo will now appear in the middle of the page during the loading process. And will then be replaced after a few seconds by React.


A
Ahmed Kamal

You don't need that much effort, here's a basic example.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="logo192.png" />
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  <title>Title</title>
  <style>
    body {
      margin: 0;
    }

    .loader-container {
      width: 100vw;
      height: 100vh;
      display: flex;
      overflow: hidden;
    }

    .loader {
      margin: auto;
      border: 5px dotted #dadada;
      border-top: 5px solid #3498db;
      border-radius: 50%;
      width: 100px;
      height: 100px;
      -webkit-animation: spin 2s linear infinite;
      animation: spin 2s linear infinite;
    }

    @-webkit-keyframes spin {
      0% {
        -webkit-transform: rotate(0deg);
      }

      100% {
        -webkit-transform: rotate(360deg);
      }
    }

    @keyframes spin {
      0% {
        transform: rotate(0deg);
      }

      100% {
        transform: rotate(360deg);
      }
    }

  </style>
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root">
    <div class="loader-container">
      <div class="loader"></div>
    </div>
  </div>
</body>

</html>

You can play around with HTML and CSS to make it looks like your example.


D
Doublers Kay

This problem can be easily solved with the lazy feature of React.

import { Suspense, lazy } from "react"
import Loading from "components/Loading"

const Dashboard = lazy(() => import("containers/Dashboard"))

const App = () => (
  <Suspense fallback={<Loading />}>
    <Dashboard />
  </Suspense>
)

export default App

The loading component will be showing while there is a Dashboard component still loading.


This is a great solution, not sure why it didn't get more traction! reactjs.org/docs/react-api.html#reactlazy
This only works after React has been downloaded as part of your bundle though. So you still see a white screen until that happens.
L
Leniel Maccaferri

The starting of react app is based on the main bundle download. React app only starts after the main bundle being downloaded in the browser. This is even true in case of lazy loading architecture. But the fact is we cannot exactly state the name of any bundles. Because webpack will add a hash value at the end of each bundle at the time when you run 'npm run build' command. Of course we can avoid that by changing hash settings, but it will seriously affect the cache data problem in the Browser. Browsers might not take the new version because of the same bundle name. . we need a webpack + js + CSS approach to handle this situation.

change the public/index.html as below

App Name

loading

In your production webpack configuration change the HtmlWebpackPlugin option to below

 new HtmlWebpackPlugin({
          inject: false,
...

https://i.stack.imgur.com/e0hwd.png


N
Nipun Jain

I also used @Ori Drori's answer and managed to get it to work. As your React code grows, so will the bundles compiled that the client browser will have to download on first time access. This imposes a user experience issue if you don't handle it well.

What I added to @Ori answer was to add and execute the onload function in the index.html on onload attribute of the body tag, so that the loader disappear after everything has been fully loaded in the browse, see the snippet below:

<html>
  <head>
     <style>
       .loader:empty {
          position: absolute;
          top: calc(50% - 4em);
          left: calc(50% - 4em);
          width: 6em;
          height: 6em;
          border: 1.1em solid rgba(0, 0, 0, 0.2);
          border-left: 1.1em solid #000000;
          border-radius: 50%;
          animation: load8 1.1s infinite linear;
        }
        @keyframes load8 {
          0% {
           transform: rotate(0deg);
          }
          100% {
           transform: rotate(360deg);
          }
        }
     </style>
     <script>
       function onLoad() {
         var loader = document.getElementById("cpay_loader");loader.className = "";}
     </script>
   </head>
   <body onload="onLoad();">
     more html here.....
   </body>
</html>

r
roqkabel

What about using Pace

Use this link address here.

https://github.hubspot.com/pace/docs/welcome/

1.On their website select the style you want and paste in index.css

2.go to cdnjs Copy the link for Pace Js and add to your script tags in public/index.html

3.It automatically detect web loads and displays the pace at the browser Top.

You can also modify the height and animation in the css also.


Awesome and can be integrated in no time.
u
user3605834

The most important question is: what do you mean by 'loading'? If you are talking about the physical element being mounted, some of the first answers here are great. However, if the first thing your app does is check for authentication, what you are really loading is data from the backend whether the user passed a cookie that labels them an authorized or unauthorized user.

This is based around redux, but you can do easily change it to plain react state model.

action creator:

export const getTodos = () => {
  return async dispatch => {
    let res;
    try {
      res = await axios.get('/todos/get');

      dispatch({
        type: AUTH,
        auth: true
      });
      dispatch({
        type: GET_TODOS,
        todos: res.data.todos
      });
    } catch (e) {
    } finally {
      dispatch({
        type: LOADING,
        loading: false
      });
    }
  };
};

The finally part means whether the user is authed or not, the loading screen goes away after a response is received.

Here's what a component that loads it could look like:

class App extends Component {
  renderLayout() {
    const {
      loading,
      auth,
      username,
      error,
      handleSidebarClick,
      handleCloseModal
    } = this.props;
    if (loading) {
      return <Loading />;
    }
    return (
      ...
    );
  }

  ...

  componentDidMount() {
    this.props.getTodos();
  }

...

  render() {
    return this.renderLayout();
 }

}

If state.loading is truthy, we will always see a loading screen. On componentDidMount, we call our getTodos function, which is an action creator that turns state.loading falsy when we get a response (which can be an error). Our component updates, calls render again, and this time there is no loading screen because of the if statement.


B
Bruno67v

From React Documentation, source.

React.lazy function lets you render a dynamic import as a regular component. This will automatically load the bundle containing the OtherComponent when this component is first rendered. React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component. The lazy component should then be rendered inside a Suspense component, which allows us to show some fallback content (such as a loading indicator) while we’re waiting for the lazy component to load.

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

The fallback prop accepts any React elements that you want to render while waiting for the component to load. You can place the Suspense component anywhere above the lazy component. You can even wrap multiple lazy components with a single Suspense component.

Source


关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now