13

I'm currently using React 16 with Suspense and Lazy to code-split my codebase. Although I would like to preload components.

In my example below I got two routes. Is there a way to preload Demo as soon Prime did mount? I've tried to create another dynamic import in the componentDidMount of the Prime page, but React.lazy doesn't seem to get that that's the same file as the dynamic import below.

import React, { lazy, Suspense } from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import GlobalStyle from 'styles';

import Loading from 'common/Loading';
const Prime = lazy(() => import(/* webpackChunkName: "Prime" */'modules/Prime'));
const Demo = lazy(() => import(/* webpackChunkName: "Demo" */'modules/Demo'));

const App = () => (
  <main>
    <GlobalStyle />
    <Suspense fallback={<Loading>Loading...</Loading>}>
      <Switch>
        <Route path="/" component={Prime} exact />
        <Route path="/demo" component={Demo} />
      </Switch>
    </Suspense>
  </main>
);

export default withRouter(App);

So I've tried different approaches, for example with and without webpackChunkName and different ways of importing the other component in componentDidMount, as seen below. The first two approaches of importing the file in componentDidMount resulted in a Webpack error shown at the bottom of the image below. Only the third proceeded, but made the file 2.[hash].js in the image, only load after the page was visited, and not on componentDidMount

enter image description here

What am I missing here?

Code of modules/Demo.jsx:

import React from 'react';

import LogoIcon from 'vectors/logo.svg';
import PageLink from 'common/PageLink';
import Anchor from 'common/Anchor';
import CenteredSection from 'common/CenteredSection';

const Demo = () => (
  <CenteredSection variant="green">
    <LogoIcon />
    <PageLink to="/" variant="green">Go to home page</PageLink>
  </CenteredSection>
);

export default Demo;
ronnyrr
  • 1,481
  • 3
  • 26
  • 45
  • Using `lazy` within componentDidMount of Prime won't do you any good for the preload case since it causes the component to be loaded when it is rendered. Approaches 2 and 3 seem fine at first glance, but the error you're getting for 1 and 2 makes me want to see the code for `modules/Demo` since it seems that something is causing a cyclic dependency. – Ryan Cogswell Nov 11 '18 at 21:05
  • @RyanC thanks for your response. I've added the code of the Demo page. You can also view to codebase over here: https://github.com/JBostelaar/react-prime/tree/progressive-web-app/src I also thought the first approach couldn't work, but still I wanted to show what I've tried. 2 gives the error, 3 doesn't preload the page. – ronnyrr Nov 11 '18 at 21:45
  • The code structure is a little different than I was expecting. I was expecting to see `modules/Demo.jsx` and `modules/Prime.jsx`. I'm not saying that what you have is "wrong", but having `Demo/index.jsx` instead just deviates from approaches that I have used that I can give more confident direction on. – Ryan Cogswell Nov 11 '18 at 21:55
  • Thanks for your input, but I’m pretty sure that the issue doesn’t come from my folder structure or naming. – ronnyrr Nov 11 '18 at 22:00
  • Nice webpackChunkName usage. Didn't know you could name your chunks that way, thanks ;) – webmaster Mar 08 '19 at 15:17

3 Answers3

18

This is incredibly easy to do, I think there is a misunderstanding about what lazy() and Suspense are doing under the hood.

The only expectation that React.lazy() has is that it takes a function that returns a Promise that resolves with a default component.

React.lazy(() => Promise<{default: MyComponent}>)

So if you want to preload, all you have to do is execute the promise yourself ahead of time.

// So change this, which will NOT preload
import React from 'react';
const MyLazyComp = React.lazy(() => import('./path/to/component'));

/*********************************************/

// To this, which WILL preload
import React from 'react';

// kicks off immediately when the current file is imported
const componentPromise = import('./path/to/component');

// by the time this gets rendered, your component is probably already loaded
// Suspense still works exactly the same with this.
const MyLazyComp = React.lazy(() => componentPromise);

The fact that this is a known signature makes it for useful for all sorts of other situations. For instance, I have a bunch of components that rely on the google maps api being dynamically loaded, I was able to create a function that loads the google maps api and then imports the component. I won't detail the internals of this example since it's a tangent, but the point is I made myself a function that does a bunch of async stuff and then returns a Promise with an object of {default: Component}.

import React from 'react';
const MyLazyComp = React.lazy(() => importMapsComponent('./path/to/comp'));
James Friedman
  • 246
  • 2
  • 4
  • But with this change it will just simply load the lazy import file on Application load, so what's the use of Lazy here? – Pardeep Jain Mar 21 '23 at 06:07
1

Not sure how much help this will be, but here is a code sandbox that works (Demo gets loaded by componentDidMount). It is a considerably simplified version of your code using create-react-app for the config. Perhaps you can take this as a starting point and morph it gradually closer to your app to see what causes the dynamic import to no longer work as desired.

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
0

In my case this problem was further complicated by a separation between the lazy and the component that needed to initiate preloading.

I found you can simply use a line like:

void (async () => import('the-lazy-import'))();

to preload the import, even without having access to the lazy component.