Reputation: 9055
I am wondering if there is a good way to prevent the flashing of the fallback in react. I am using react router and the issue is that when a component gets suspended the fallback loader flashes really quick and it is pretty annoying. I saw the answer here React suspense/lazy delay? which would look like the following:
const Home = lazy(() => {
return Promise.all([
import('./components/Home'),
new Promise(resolve => setTimeout(resolve, 500))
]).then(([moduleExports]) => moduleExports);
});
but my issue with this is that I have an overlay loading spinner with a transparent background and the component doesn't actually load until the promises are resolved. This leaves the page hanging without content for a half of a second and is actually more annoying then the flashing of the spinner.
So I guess the question is has anyone found a good way to deal with this issue. I would really like to add something like nprogress to the page but can't figure out how I would implement this with React.suspense. I may just have to go back to using react loadable but I really don't want to when react comes with basically the same functionality out of the box.
Upvotes: 19
Views: 12008
Reputation: 754
I was in this scenario couple of weeks ago, wherein I want the Suspense fallback not to render, we all know that the fallback will get display if the lazy components are in state of downloading
well that is normal behavior of Suspense there is nothing to do with that, instead we could trick the perspective of our end users by copying the partial backdrop design of our web page
/* style.css */
.main-pane {
width: '100%',
height: '100%',
backgroundColor: #deb887
}
.header-pane {
width: '100%',
height: '100%',
position: 'absolute',
top: 0,
backgroundColor: #424242
}
//EmptyPage.js
import styles from './style.css';
const EmptyPage = () => {
return (
<div style={styles['main-pane']}>
<div style={styles['header-pane']}><div/>
</div>
)
}
//MainPage.js
import styles from './style.css';
const MainPage = () => {
return (
<div style={styles['main-pane']}>
<div style={styles['header-pane']}>HEADER CONTENT<div/>
BODY CONTENT
</div>
)
}
//App.js
const MainPage = lazy(() => import('./pages/MainPage'))
import EmptyPage from './pages/EmptyPage';
import styles from './style.css';
const App = () => {
return (
<Suspense fallback={<EmptyPage/>}>
<Router>
...
<MainPage/>
</Router>
</Suspense>
)
}
So the goal is just to copy the partial backdrop design instead of flashing or something that is not a friendly experience to the perspective of our end users
Upvotes: 0
Reputation: 1547
I recently was facing the same issue and I ended up with this implementation of a component that I use for the fallback
of the suspense.
The idea is simple, just don't show anything for a certain amount of time, then show the loader. So let's say for like 300ms there won't be anything displayed, after the delay it should display (in this case) the ContentLoader
or anything you like.
In Typescript as lazy-loader.ts
import { FC, useEffect, useState } from 'react';
import ContentLoader from './content-loader'; // or any spinner component
export interface LazyLoaderProps {
delay?: number;
}
const LazyLoader: FC<LazyLoaderProps> = ({
delay = 250,
...props
}) => {
const [show, setShow] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => {
setShow(true);
}, delay);
return () => {
clearTimeout(timeout);
};
}, [delay]);
return show ? <ContentLoader {...props} /> : null;
};
export { LazyLoader as default };
then use like so
import LazyLoader from "./lazy-loader"
// ...
<Suspense fallback={<LazyLoader delay={300} />}>...</Suspense>
That does not delay the import. (which I also thought was not optimal) Let me know if this helps.
Upvotes: 3
Reputation: 225
import React, { Suspense, lazy } from "react";
const Home = lazy(() => {
return Promise.all([
import("./home"),
new Promise(resolve => setTimeout(resolve, 300))
]).then(([moduleExports]) => moduleExports);
});
function FullSpinner() {
return (
{/** full spinner jsx goes here */}
<div className="full-spinner">
<p>loading....</p>
</div>
)
}
function App() {
return (
<div className="App">
<h1>app component</h1>
<Suspense fallback={<FullSpinner />}>
<Home />
</Suspense>
</div>
);
}
Edit:
import React, { Suspense, lazy, useState, useEffect } from "react";
const Home = lazy(() => {
return Promise.all([
import("./home"),
new Promise(resolve => setTimeout(resolve, 500))
]).then(([moduleExports]) => moduleExports);
});
function FullSpinner() {
return (
<div className="full-spinner">
<p>loading....</p>
</div>
);
}
const LazyLoading = ({ delay, loader: Loader, children }) => {
const [ready, setReady] = useState(false);
useEffect(() => {
setTimeout(() => setReady(true), delay);
}, [delay]);
return ready ? (
<Suspense fallback={<Loader />}>{children}</Suspense>
) : (
<Loader />
);
};
function App() {
return (
<div className="App">
<h1>app component</h1>
<LazyLoading delay={2000} loader={FullSpinner}>
<Home />
</LazyLoading>
</div>
);
}
Upvotes: -2