This is an example from Google Adsense application page. The loading screen displayed before the main page showed after.
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.
This question is related to
reactjs
asynchronous
redux
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
<!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_
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.
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).
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.
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.
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 {_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_
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 {_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_
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');_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_
Example 2 - hooks
This example uses the useEffect
hook to hide the spinner after the component mounts.
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_
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.
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()
Source: Stackoverflow.com