19

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.

Steve K
  • 8,505
  • 2
  • 20
  • 35
  • 1
    Maybe this is what you want: https://stackoverflow.com/a/58971161/6548802 The loader will show only if the loading is greater than 300ms. You'll have to customize this delayed loader to display what you want or a generic component. – known-as-bmf Aug 01 '20 at 12:52
  • I got the same problem. But i am using react-snap to generate static version. If this is the same case, i resolved removing lazy from top components and got a partial flash, according the user viewport. https://github.com/facebook/react/issues/14438 – Leonardo Lima Sep 28 '20 at 12:19
  • Does this answer your question? [React suspense/lazy delay?](https://stackoverflow.com/questions/54158994/react-suspense-lazy-delay) – Kevin Ashworth Oct 09 '20 at 16:33
  • @KevinAshworth the link you posted is basically the exact same code that is in my question and I address it if you read the question. The problem is that the page does not have any content in it during the timeout and the page hangs and jumps around. So no it does not answer the question. – Steve K Oct 09 '20 at 21:05

3 Answers3

3

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.

Julian Kleine
  • 1,539
  • 6
  • 14
0

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

Marvin
  • 647
  • 7
  • 15
-2
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>
  );
}
Hamid Pouladi
  • 215
  • 2
  • 4
  • This is basically the exact same code I have posted above. How will this help at all. Maybe you didn't understand the question. When the component loads but is suspended react shows the spinner really fast then takes it away. This quick flashing then removing of the spinner by suspese is what I am trying to avoid. I don't need help writing the spinner code. – Steve K Aug 08 '19 at 06:19