React Suspense lazy loading without fallback
Asked Answered
K

5

20

I want to lazy load my components to decrease my initial bundle size and get components on the fly using code splitting using react router.

However, when using React Suspense, they force you to use a fallback for loading.
This wouldn't work:

const lazyLoadComponent = Component =>
    props => (
        <Suspense> // Missing fallback property
            <Component {...props} />
        </Suspense>
    );

In my case I am rendering html from the server so I don't want to use a spinner.
This would create a useless flicker on my screen! I.e.:

  • Html loads
  • Place holder appears
  • PageComponent for the route gets loaded
  • I have my own spinner that loads a feed from within the page component

In my case the html corresponds to the react component that gets loaded.

Is there any known hack to easily work around this problem (except for creating a loader for any route that copies the html (!!), which by the way, would make lazy loading useless).

I am a bit displeased with "forcing" us to add a loader and I don't understand the logic behind the decision to make it mandatory.

Kayleigh answered 27/8, 2020 at 22:27 Comment(0)
K
2

I created an issue for this on Github: https://github.com/facebook/react/issues/19715

There isn't a current clean solution using React-Router / React.
This is however foreseen in a future release using concurrent mode. As mentioned by Dan Abramov:

Regarding your concrete feature request, I think I can reframe it slightly differently. It's not that you want "optional fallback" since that wouldn't make sense for new screens (we've got to show something). What I believe you're looking for is a way to skip showing the fallback if the content is already in HTML. This is precisely how React behaves in Concurrent Mode so the feature request is already implemented (and will eventually become the default behavior in a stable release).

For me it is not a problem to wait, so currently I will omit lazy-loading the routes as this concerns a hobby-project and I have time to wait for a future release.

Kayleigh answered 28/8, 2020 at 14:41 Comment(0)
U
8

Try to use code splitting in the docs

fallback props is just a React element, you can set it for null.

const MyLazyComponent= React.lazy(() => import('./MyComponent'));

<Suspense fallback={null}>
    <MyLazyComponent />
</Suspense>
Uptotheminute answered 27/8, 2020 at 23:0 Comment(1)
That doesn't help. It still renders some empty div (or whatever it is that causes the flicker). I just want it to skip show anything at all and just render the component when it's available. I'm already using code splitting with webpack, which works well.Kayleigh
K
2

I created an issue for this on Github: https://github.com/facebook/react/issues/19715

There isn't a current clean solution using React-Router / React.
This is however foreseen in a future release using concurrent mode. As mentioned by Dan Abramov:

Regarding your concrete feature request, I think I can reframe it slightly differently. It's not that you want "optional fallback" since that wouldn't make sense for new screens (we've got to show something). What I believe you're looking for is a way to skip showing the fallback if the content is already in HTML. This is precisely how React behaves in Concurrent Mode so the feature request is already implemented (and will eventually become the default behavior in a stable release).

For me it is not a problem to wait, so currently I will omit lazy-loading the routes as this concerns a hobby-project and I have time to wait for a future release.

Kayleigh answered 28/8, 2020 at 14:41 Comment(0)
B
2

So basically all we have to do is, wrap the loader in a component and load the script using that component (Loadable here). This way we can use React.lazy wihtout any animation.

Since our fallback is empty you wont see any animation and your content will be visible always. There will not be any flicker of content hiding.

loadable.js

import React, { Suspense } from 'react';

const Loadable = (Component) => (props) => (
  <Suspense fallback={<></>}>
    <Component {...props} />
  </Suspense>
);

export default Loadable;

We can wrap the import in Loadable so that it will load lazy. route.js

import React, { lazy } from 'react';
import Loadable from './components/Loadable';

// page import
const Home = Loadable(lazy(() => import('./pages/home')));

// define routes
const routes = [
    {
        path: "dashboard",
        element: <Layout />,
        children: [
            {
                path: "",
                element: <Home />,
            },
        ]
    },
]
Barogram answered 9/7, 2022 at 4:43 Comment(0)
F
1

I also faced the same flickering issue when I tried to open a modal component which was configured to lazily load. I got it solved by using a startTransition

import { startTransition } from 'react';

function ComponentA() {
  const [openModal, setOpenModal] = useState(false);

  const modalHandler = () => {
     startTransition(() => {
       setOpenModal(!openModal)
     });
  }
  
}

You can find more details about this using below react documentation.
https://react.dev/reference/react/Suspense#preventing-already-revealed-content-from-hiding

Furcate answered 7/4 at 2:31 Comment(0)
F
0

In my experience (with React 17), there's no flickering happens if you pass null to fallback param.

I have a Modal component that renders lazy components.

Here's my Typescript solution:

type LazyLoadHOC = {
   component: React.LazyExoticComponent<any>,
   fallback?: React.ComponentType | null,
   [x:string]: any
};

export const LazyLoad: React.FC<LazyLoadHOC> = ({
     component: Component, fallback = null, ...props
}) => {
  return (
    <React.Suspense fallback={fallback}>
        <Component {...props} />
    </React.Suspense>
  );
};

Here's my Modal:

const AddressFormModel = React.lazy(() => import('@components/address/address-form-modal'));

<Modal show={isOpen} backdrop={'static'} dialogClassName='custom-modal'>
    <ModalBody>
       {view === 'ADDRESS-FORM' && <LazyLoad component={AddressFormModel} />}
    </ModalBody>
</Modal>

This will ensure to not trigger your global React.Suspense.

Fatma answered 26/5, 2022 at 18:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.