[reactjs] 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.

enter image description here

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.

This question is related to reactjs asynchronous redux

The answer is


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.


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>

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.


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;

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

_x000D_
_x000D_
<!DOCTYPE html>_x000D_
<html lang="en" xml:lang="en">_x000D_
_x000D_
<head>_x000D_
  <meta charset="utf-8">_x000D_
  <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=3.0, shrink-to-fit=no">_x000D_
  <meta name="theme-color" content="#000000">_x000D_
  <!--_x000D_
      manifest.json provides metadata used when your web app is added to the_x000D_
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/_x000D_
    -->_x000D_
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json">_x000D_
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">_x000D_
  <style>_x000D_
 .percentage {_x000D_
      position: absolute;_x000D_
      top: 50%;_x000D_
      left: 50%;_x000D_
      width: 150px;_x000D_
      height: 150px;_x000D_
      border: 1px solid #ccc;_x000D_
      background-color: #f3f3f3;_x000D_
      -webkit-transform: translate(-50%, -50%);_x000D_
          -ms-transform: translate(-50%, -50%);_x000D_
              transform: translate(-50%, -50%);_x000D_
      border: 1.1em solid rgba(0, 0, 0, 0.2);_x000D_
      border-radius: 50%;_x000D_
      overflow: hidden;_x000D_
      display: -webkit-box;_x000D_
      display: -ms-flexbox;_x000D_
      display: flex;_x000D_
      -webkit-box-pack: center;_x000D_
          -ms-flex-pack: center;_x000D_
              justify-content: center;_x000D_
      -webkit-box-align: center;_x000D_
          -ms-flex-align: center;_x000D_
              align-items: center;_x000D_
    }_x000D_
_x000D_
    .innerpercentage {_x000D_
      font-size: 20px;_x000D_
    }_x000D_
  </style>_x000D_
  <script>_x000D_
    function showPercentage(value) {_x000D_
      document.getElementById('percentage').innerHTML = (value * 100).toFixed() + "%";_x000D_
    }_x000D_
    var req = new XMLHttpRequest();_x000D_
    req.addEventListener("progress", function (event) {_x000D_
      if (event.lengthComputable) {_x000D_
        var percentComplete = event.loaded / event.total;_x000D_
        showPercentage(percentComplete)_x000D_
        // ..._x000D_
      } else {_x000D_
        document.getElementById('percentage').innerHTML = "Loading..";_x000D_
      }_x000D_
    }, false);_x000D_
_x000D_
    // load responseText into a new script element_x000D_
    req.addEventListener("load", function (event) {_x000D_
      var e = event.target;_x000D_
      var s = document.createElement("script");_x000D_
      s.innerHTML = e.responseText;_x000D_
      document.documentElement.appendChild(s);_x000D_
      document.getElementById('parentDiv').style.display = 'none';_x000D_
_x000D_
    }, false);_x000D_
_x000D_
    var bundleName = "<%= htmlWebpackPlugin.files.chunks.main.entry %>";_x000D_
    req.open("GET", bundleName);_x000D_
    req.send();_x000D_
_x000D_
  </script>_x000D_
  <!--_x000D_
      Notice the use of %PUBLIC_URL% in the tags above._x000D_
      It will be replaced with the URL of the `public` folder during the build._x000D_
      Only files inside the `public` folder can be referenced from the HTML._x000D_
_x000D_
      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will_x000D_
      work correctly both with client-side routing and a non-root public URL._x000D_
      Learn how to configure a non-root public URL by running `npm run build`._x000D_
    -->_x000D_
_x000D_
  <title>App Name</title>_x000D_
  <link href="<%= htmlWebpackPlugin.files.chunks.main.css[0] %>" rel="stylesheet">_x000D_
</head>_x000D_
_x000D_
<body>_x000D_
  <noscript>_x000D_
    You need to enable JavaScript to run this app._x000D_
  </noscript>_x000D_
  <div id="parentDiv" class="percentage">_x000D_
    <div id="percentage" class="innerpercentage">loading</div>_x000D_
  </div>_x000D_
  <div id="root"></div>_x000D_
  <!--_x000D_
      This HTML file is a template._x000D_
      If you open it directly in the browser, you will see an empty page._x000D_
_x000D_
      You can add webfonts, meta tags, or analytics to this file._x000D_
      The build step will place the bundled scripts into the <body> tag._x000D_
_x000D_
      To begin the development, run `npm start` or `yarn start`._x000D_
      To create a production bundle, use `npm run build` or `yarn build`._x000D_
    -->_x000D_
</body>_x000D_
_x000D_
</html>
_x000D_
_x000D_
_x000D_

In your production webpack configuration change the HtmlWebpackPlugin option to below

 new HtmlWebpackPlugin({
          inject: false,
...

You may need to use 'eject' command to get the configuration file. latest webpack might have the option to configure the HtmlWebpackPlugin without ejecting project. enter image description here


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.


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.


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

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)


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.


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;
    ...
}

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.


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

_x000D_
_x000D_
class App extends React.Component {_x000D_
  state = {_x000D_
    loading: true_x000D_
  };_x000D_
_x000D_
  componentDidMount() {_x000D_
    // this simulates an async action, after which the component will render the content_x000D_
    demoAsyncCall().then(() => this.setState({ loading: false }));_x000D_
  }_x000D_
  _x000D_
  render() {_x000D_
    const { loading } = this.state;_x000D_
    _x000D_
    if(loading) { // if your component doesn't have to wait for an async action, remove this block _x000D_
      return null; // render null when app is not ready_x000D_
    }_x000D_
    _x000D_
    return (_x000D_
      <div>I'm the app</div>_x000D_
    ); _x000D_
  }_x000D_
}_x000D_
_x000D_
function demoAsyncCall() {_x000D_
  return new Promise((resolve) => setTimeout(() => resolve(), 2500));_x000D_
}_x000D_
_x000D_
ReactDOM.render(_x000D_
  <App />,_x000D_
  document.getElementById('app')_x000D_
);
_x000D_
.loader:empty {_x000D_
  position: absolute;_x000D_
  top: calc(50% - 4em);_x000D_
  left: calc(50% - 4em);_x000D_
  width: 6em;_x000D_
  height: 6em;_x000D_
  border: 1.1em solid rgba(0, 0, 0, 0.2);_x000D_
  border-left: 1.1em solid #000000;_x000D_
  border-radius: 50%;_x000D_
  animation: load8 1.1s infinite linear;_x000D_
}_x000D_
_x000D_
@keyframes load8 {_x000D_
  0% {_x000D_
    transform: rotate(0deg);_x000D_
  }_x000D_
  100% {_x000D_
    transform: rotate(360deg);_x000D_
  }_x000D_
}
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>_x000D_
_x000D_
<div id="app" class="loader"></div> <!-- add class loader to container -->
_x000D_
_x000D_
_x000D_

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 (+):

_x000D_
_x000D_
class App extends React.Component {_x000D_
  state = {_x000D_
    loading: true_x000D_
  };_x000D_
_x000D_
  componentDidMount() {_x000D_
    // this simulates an async action, after which the component will render the content_x000D_
    demoAsyncCall().then(() => this.setState({ loading: false }));_x000D_
  }_x000D_
  _x000D_
  render() {_x000D_
    const { loading } = this.state;_x000D_
    _x000D_
    if(loading) { // if your component doesn't have to wait for async data, remove this block _x000D_
      return null; // render null when app is not ready_x000D_
    }_x000D_
    _x000D_
    return (_x000D_
      <div>I'm the app</div>_x000D_
    ); _x000D_
  }_x000D_
}_x000D_
_x000D_
function demoAsyncCall() {_x000D_
  return new Promise((resolve) => setTimeout(() => resolve(), 2500));_x000D_
}_x000D_
_x000D_
ReactDOM.render(_x000D_
  <App />,_x000D_
  document.getElementById('app')_x000D_
);
_x000D_
#app:not(:empty) + .sk-cube-grid {_x000D_
  display: none;_x000D_
}_x000D_
_x000D_
.sk-cube-grid {_x000D_
  width: 40px;_x000D_
  height: 40px;_x000D_
  margin: 100px auto;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube {_x000D_
  width: 33%;_x000D_
  height: 33%;_x000D_
  background-color: #333;_x000D_
  float: left;_x000D_
  animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube1 {_x000D_
  animation-delay: 0.2s;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube2 {_x000D_
  animation-delay: 0.3s;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube3 {_x000D_
  animation-delay: 0.4s;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube4 {_x000D_
  animation-delay: 0.1s;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube5 {_x000D_
  animation-delay: 0.2s;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube6 {_x000D_
  animation-delay: 0.3s;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube7 {_x000D_
  animation-delay: 0s;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube8 {_x000D_
  animation-delay: 0.1s;_x000D_
}_x000D_
_x000D_
.sk-cube-grid .sk-cube9 {_x000D_
  animation-delay: 0.2s;_x000D_
}_x000D_
_x000D_
@keyframes sk-cubeGridScaleDelay {_x000D_
  0%,_x000D_
  70%,_x000D_
  100% {_x000D_
    transform: scale3D(1, 1, 1);_x000D_
  }_x000D_
  35% {_x000D_
    transform: scale3D(0, 0, 1);_x000D_
  }_x000D_
}
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>_x000D_
_x000D_
<div id="app"></div>_x000D_
<!-- add class loader to container -->_x000D_
_x000D_
<div class="sk-cube-grid">_x000D_
  <div class="sk-cube sk-cube1"></div>_x000D_
  <div class="sk-cube sk-cube2"></div>_x000D_
  <div class="sk-cube sk-cube3"></div>_x000D_
  <div class="sk-cube sk-cube4"></div>_x000D_
  <div class="sk-cube sk-cube5"></div>_x000D_
  <div class="sk-cube sk-cube6"></div>_x000D_
  <div class="sk-cube sk-cube7"></div>_x000D_
  <div class="sk-cube sk-cube8"></div>_x000D_
  <div class="sk-cube sk-cube9"></div>_x000D_
</div>
_x000D_
_x000D_
_x000D_


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

_x000D_
_x000D_
const loader = document.querySelector('.loader');_x000D_
_x000D_
// if you want to show the loader when React loads data again_x000D_
const showLoader = () => loader.classList.remove('loader--hide');_x000D_
_x000D_
const hideLoader = () => loader.classList.add('loader--hide');_x000D_
_x000D_
class App extends React.Component {_x000D_
  componentDidMount() {_x000D_
    this.props.hideLoader();_x000D_
  }_x000D_
  _x000D_
  render() {   _x000D_
    return (_x000D_
      <div>I'm the app</div>_x000D_
    ); _x000D_
  }_x000D_
}_x000D_
_x000D_
// the setTimeout simulates the time it takes react to load, and is not part of the solution_x000D_
setTimeout(() => _x000D_
  // the show/hide functions are passed as props_x000D_
  ReactDOM.render(_x000D_
    <App_x000D_
      hideLoader={hideLoader}_x000D_
      showLoader={showLoader} _x000D_
      />,_x000D_
    document.getElementById('app')_x000D_
  )_x000D_
, 1000);
_x000D_
.loader {_x000D_
  position: absolute;_x000D_
  top: calc(50% - 4em);_x000D_
  left: calc(50% - 4em);_x000D_
  width: 6em;_x000D_
  height: 6em;_x000D_
  border: 1.1em solid rgba(0, 0, 0, 0.2);_x000D_
  border-left: 1.1em solid #000000;_x000D_
  border-radius: 50%;_x000D_
  animation: load8 1.1s infinite linear;_x000D_
  transition: opacity 0.3s;_x000D_
}_x000D_
_x000D_
.loader--hide {_x000D_
  opacity: 0;_x000D_
}_x000D_
_x000D_
@keyframes load8 {_x000D_
  0% {_x000D_
    transform: rotate(0deg);_x000D_
  }_x000D_
  100% {_x000D_
    transform: rotate(360deg);_x000D_
  }_x000D_
}
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>_x000D_
_x000D_
<div id="app"></div>_x000D_
_x000D_
<div class="loader"></div>
_x000D_
_x000D_
_x000D_

Example 2 - hooks

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

_x000D_
_x000D_
const { useEffect } = React;_x000D_
_x000D_
const loader = document.querySelector('.loader');_x000D_
_x000D_
// if you want to show the loader when React loads data again_x000D_
const showLoader = () => loader.classList.remove('loader--hide');_x000D_
_x000D_
const hideLoader = () => loader.classList.add('loader--hide');_x000D_
_x000D_
const App = ({ hideLoader }) => {_x000D_
  useEffect(hideLoader, []);_x000D_
  _x000D_
  return (_x000D_
    <div>I'm the app</div>_x000D_
  ); _x000D_
}_x000D_
_x000D_
// the setTimeout simulates the time it takes react to load, and is not part of the solution_x000D_
setTimeout(() => _x000D_
  // the show/hide functions are passed as props_x000D_
  ReactDOM.render(_x000D_
    <App_x000D_
      hideLoader={hideLoader}_x000D_
      showLoader={showLoader} _x000D_
      />,_x000D_
    document.getElementById('app')_x000D_
  )_x000D_
, 1000);
_x000D_
.loader {_x000D_
  position: absolute;_x000D_
  top: calc(50% - 4em);_x000D_
  left: calc(50% - 4em);_x000D_
  width: 6em;_x000D_
  height: 6em;_x000D_
  border: 1.1em solid rgba(0, 0, 0, 0.2);_x000D_
  border-left: 1.1em solid #000000;_x000D_
  border-radius: 50%;_x000D_
  animation: load8 1.1s infinite linear;_x000D_
  transition: opacity 0.3s;_x000D_
}_x000D_
_x000D_
.loader--hide {_x000D_
  opacity: 0;_x000D_
}_x000D_
_x000D_
@keyframes load8 {_x000D_
  0% {_x000D_
    transform: rotate(0deg);_x000D_
  }_x000D_
  100% {_x000D_
    transform: rotate(360deg);_x000D_
  }_x000D_
}
_x000D_
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>_x000D_
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>_x000D_
_x000D_
<div id="app"></div>_x000D_
_x000D_
<div class="loader"></div>
_x000D_
_x000D_
_x000D_


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

enter image description here

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

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;


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().


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

The workaround for this is:

In your render function do 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


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


Examples related to reactjs

Error: Node Sass version 5.0.0 is incompatible with ^4.0.0 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Template not provided using create-react-app How to resolve the error on 'react-native start' Element implicitly has an 'any' type because expression of type 'string' can't be used to index Invalid hook call. Hooks can only be called inside of the body of a function component How to style components using makeStyles and still have lifecycle methods in Material UI? React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? Unable to load script.Make sure you are either running a Metro server or that your bundle 'index.android.bundle' is packaged correctly for release

Examples related to asynchronous

How to read file with async/await properly? Use Async/Await with Axios in React.js Waiting until the task finishes How to reject in async/await syntax? React - Display loading screen while DOM is rendering? angular 2 how to return data from subscribe How do I access store state in React Redux? SyntaxError: Unexpected token function - Async Await Nodejs Why does .json() return a promise? Why is setState in reactjs Async instead of Sync?

Examples related to redux

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop Receiving "Attempted import error:" in react app Local package.json exists, but node_modules missing What is the best way to redirect a page using React Router? How do I fix "Expected to return a value at the end of arrow function" warning? ReactJS lifecycle method inside a function Component How to overcome the CORS issue in ReactJS Attach Authorization header for all axios requests React - Display loading screen while DOM is rendering?