252

This is an example from the Google AdSense application page. The loading screen is displayed before the main page is shown.

enter image description here

I am not sure how to achieve the same effect with React because if I render the loading screen as a React component, it will not be displayed while the page is loading as it needs to wait for the DOM to be rendered first.

Updated:

As a solution, I tried an approach where I placed the screen loader in index.html file and removed it in the componentDidMount() lifecycle method of React component.

Example and react-loading-screen.

Thanh Nguyen
  • 5,174
  • 11
  • 43
  • 74
  • 1
    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. – FurkanO Dec 06 '16 at 03:54
  • 2
    This is simply wonderful! Thank you. – Arman Karimi Mar 18 '20 at 12:31
  • 2
    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.
    – cnanders Jan 21 '21 at 22:16

25 Answers25

271

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 (
      <div>I'm the app</div>
    ); 
  }
}

function demoAsyncCall() {
  return new Promise((resolve) => setTimeout(() => resolve(), 2500));
}

ReactDOM.render(
  <App />,
  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);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>

<div id="app" class="loader"></div> <!-- add class loader to container -->

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 (
      <div>I'm the app</div>
    ); 
  }
}

function demoAsyncCall() {
  return new Promise((resolve) => setTimeout(() => resolve(), 2500));
}

ReactDOM.render(
  <App />,
  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);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>

<div id="app"></div>
<!-- add class loader to container -->

<div class="sk-cube-grid">
  <div class="sk-cube sk-cube1"></div>
  <div class="sk-cube sk-cube2"></div>
  <div class="sk-cube sk-cube3"></div>
  <div class="sk-cube sk-cube4"></div>
  <div class="sk-cube sk-cube5"></div>
  <div class="sk-cube sk-cube6"></div>
  <div class="sk-cube sk-cube7"></div>
  <div class="sk-cube sk-cube8"></div>
  <div class="sk-cube sk-cube9"></div>
</div>

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 (
      <div>I'm the app</div>
    ); 
  }
}

// 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(
    <App
      hideLoader={hideLoader}
      showLoader={showLoader} 
      />,
    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);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>

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

<div class="loader"></div>

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 (
    <div>I'm the app</div>
  ); 
}

// 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(
    <App
      hideLoader={hideLoader}
      showLoader={showLoader} 
      />,
    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);
  }
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

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

<div class="loader"></div>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • 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? – levraininjaneer Aug 03 '18 at 13:35
  • 1
    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). – Ori Drori Aug 03 '18 at 14:44
  • That timeout does is not good when performance is taken into consideration. – dryleaf Oct 31 '18 at 23:53
  • 6
    @dryleaf - the setTimeout is not part of the solution. It simulates waiting for an async action before rendering the content. – Ori Drori Nov 01 '18 at 04:34
  • I use similar approach. I can't find anything in webpack that can help me bust cache for css file required for loader. Can you help? – hamza-jutt Aug 02 '19 at 13:15
  • That's not really related to this answer, and you should've opened another question for it. However, the solution is simple - set a hash as part of the output filename. The hash will change whenever the filename changes - https://webpack.js.org/guides/caching/#output-filenames. – Ori Drori Aug 02 '19 at 14:00
  • @OriDrori Hash in file name is not working for css file. This particular CSS file is for pre-loader. It has nothing to do with react app. How can I use webpack's outout-filename for css file? – hamza-jutt Aug 05 '19 at 11:25
  • 1
    @hamza-jutt - you should open a new question about that. – Ori Drori Aug 05 '19 at 12:20
  • 1
    `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. – KJ Sudarshan Sep 19 '20 at 00:41
148

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.

Youssouf Oumar
  • 29,373
  • 11
  • 46
  • 65
kkkkkkk
  • 7,628
  • 2
  • 18
  • 31
  • 35
    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. – rishat Dec 06 '16 at 05:57
  • 6
    I do not put the icon inside the root node of React app, it just does not feel right to me – kkkkkkk Dec 06 '16 at 06:01
  • 3
    is there any downside to this for PWAs? will it interfere with the default splash screen? – benmneb Oct 03 '20 at 09:45
  • 1
    @benmneb did it interfere ? – Farhad Jan 27 '21 at 13:20
  • How can I test this on local easily? Since local loads files quite quickly, I couldn't see the loading icon clearly. – discover Mar 18 '22 at 20:58
  • 2
    @discover Open your chrome developer tools and in the Network section, Apply throttle to mimic slow loading of your application – Yash Agarwal Jul 02 '22 at 07:23
59

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.

Youssouf Oumar
  • 29,373
  • 11
  • 46
  • 65
Amoolya S Kumar
  • 1,458
  • 9
  • 10
42

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);
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
22

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)

Carson Evans
  • 1,518
  • 1
  • 13
  • 12
Hafiz Ismail
  • 3,195
  • 1
  • 25
  • 21
  • 1
    Hey! Can you tell me how to use it with react? – uneet7 Feb 06 '19 at 10:33
  • 1
    just attach the script to `public/index.html` and choose a style. this is dead simple, amazing plugin. thank you. – PJ3 Apr 05 '19 at 17:44
  • 1
    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. – invertedSpear Jun 30 '19 at 18:10
15

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.

Artem Bernatskyi
  • 4,185
  • 2
  • 26
  • 35
rishat
  • 8,206
  • 4
  • 44
  • 69
  • 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... – Alexis Wilke Feb 18 '19 at 03:04
13

Just add content inside the <div id="root"></div> 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!

Felipe Chernicharo
  • 3,619
  • 2
  • 24
  • 32
9

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;
Badal Saibo
  • 2,499
  • 11
  • 23
Max Kalik
  • 473
  • 6
  • 15
  • 9
    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. – Cristian E. Aug 28 '20 at 16:07
7

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.

kind user
  • 40,029
  • 7
  • 67
  • 77
7

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 utilized hooks. He taught API call through classes and state and everything.

Here's his code:

class App extends React.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>
    );
  }
}

ReactDOM
  .createRoot(document.getElementById("root"))
  .render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

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:

const { useEffect, useMemo, useState } = React;

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

  useEffect(() => {
    fetch("https://swapi.dev/api/people/1/")
      .then((response) => response.json())
      .then((data) => {
        setResponse(data);
        setLoading(false);
      });
  }, []);

  const content = useMemo(
    () =>
      loading ? (
        <i className="fas fa-atom fa-spin"></i>
      ) : (
        <h1>{response.name}</h1>
      ),
    [loading, response.name]
  );

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

ReactDOM
  .createRoot(document.getElementById("root"))
  .render(<App />);
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" rel="stylesheet"/>
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Note: Here is an example of using async/await:

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

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 don't like this one XD.

Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
Uzair_07
  • 141
  • 1
  • 9
7

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.

react-router-loading

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>
Victor Trusov
  • 1,057
  • 9
  • 19
  • 1
    Very nice thx! But there still is the problem of not having a loading screen before the router is set up. – thelearner Apr 12 '22 at 20:15
5

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

5

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;
    }
  }
Rich Costello
  • 95
  • 2
  • 11
4

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;
    ...
}
adrian95999
  • 117
  • 1
  • 3
  • 17
4

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.

Ahmed Kamal
  • 2,660
  • 3
  • 21
  • 36
4

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;

4

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

4

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.

Doublers Kay
  • 176
  • 1
  • 7
3

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.

Magnus Melwin
  • 1,509
  • 1
  • 21
  • 32
2

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

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

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=3.0, shrink-to-fit=no">
  <meta name="theme-color" content="#000000">
  <!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    -->
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
  <style>
 .percentage {
      position: absolute;
      top: 50%;
      left: 50%;
      width: 150px;
      height: 150px;
      border: 1px solid #ccc;
      background-color: #f3f3f3;
      -webkit-transform: translate(-50%, -50%);
          -ms-transform: translate(-50%, -50%);
              transform: translate(-50%, -50%);
      border: 1.1em solid rgba(0, 0, 0, 0.2);
      border-radius: 50%;
      overflow: hidden;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-pack: center;
          -ms-flex-pack: center;
              justify-content: center;
      -webkit-box-align: center;
          -ms-flex-align: center;
              align-items: center;
    }

    .innerpercentage {
      font-size: 20px;
    }
  </style>
  <script>
    function showPercentage(value) {
      document.getElementById('percentage').innerHTML = (value * 100).toFixed() + "%";
    }
    var req = new XMLHttpRequest();
    req.addEventListener("progress", function (event) {
      if (event.lengthComputable) {
        var percentComplete = event.loaded / event.total;
        showPercentage(percentComplete)
        // ...
      } else {
        document.getElementById('percentage').innerHTML = "Loading..";
      }
    }, false);

    // load responseText into a new script element
    req.addEventListener("load", function (event) {
      var e = event.target;
      var s = document.createElement("script");
      s.innerHTML = e.responseText;
      document.documentElement.appendChild(s);
      document.getElementById('parentDiv').style.display = 'none';

    }, false);

    var bundleName = "<%= htmlWebpackPlugin.files.chunks.main.entry %>";
    req.open("GET", bundleName);
    req.send();

  </script>
  <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->

  <title>App Name</title>
  <link href="<%= htmlWebpackPlugin.files.chunks.main.css[0] %>" rel="stylesheet">
</head>

<body>
  <noscript>
    You need to enable JavaScript to run this app.
  </noscript>
  <div id="parentDiv" class="percentage">
    <div id="percentage" class="innerpercentage">loading</div>
  </div>
  <div id="root"></div>
  <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
</body>

</html>

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

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
2

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>
Nipun Jain
  • 999
  • 6
  • 13
2

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.

roqkabel
  • 21
  • 4
1

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.

user3605834
  • 2,590
  • 1
  • 12
  • 5
1

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

Bruno67v
  • 13
  • 3
0

This is how I do it,

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>React App</title>
    <style>
      #loader-wrapper {
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        position: fixed;
        background-color: white;
        z-index: 1000;
      }

      #loader-wrapper>img {
        width: 10vw;
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        margin: auto;
      }
    </style>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <div id="loader-wrapper">
      <img src="./svg/loader.svg" />
    </div>
  </body>
</html>

In App.js import Jquery and fadeOut the div from index.html

import { $ }  from 'react-jquery-plugin';
...
...
useEffect(() => {
  $("#loader-wrapper").fadeOut();
}, []);