Home > Back-end >  Loading React Components asynchronously
Loading React Components asynchronously

Time:01-16

I'm working on a React app with a number of different pages. As my page count has grown, so has my bundle size, so I'm trying to split out our routes into their own bundles that can be loaded after the page is done rendering.

My first attempt was to use React.lazy(), but that led to poor UX. When the user went to a new page, the page footer (part of our app shell) would snap up to the top of the screen, only to get pushed back down by the lazy-loaded content.

What I'm looking for is asynchronous, but not lazy, loading of these components. In other words, to load them after the main content but before they are needed. Ideally, I'd be able to do:

import Page1 from "./Page1"; // Landing page that needs to pop up immediately
const Page2 = import("./Page2");

function AppShell () {
  return (
    <Switch>
      <Route path="/"><Page1/></Route>
      <Route path="/page2"><Page2/></Route>
    </Switch>
  )
}

but React doesn't like unresolved promises as component definitions. Is there any way to do this? Or is there a more standard way to deal with this flickering issue?

CodePudding user response:

You can use React suspense to fallback to a loading indicator that takes up the whole viewport. This would prevent the flicker from happening when your app is loading the lazy loaded component.

For example:

const LazyPage = (Component) => (props) => (
  <React.Suspense fallback={<FullPageSpinner />}>
    <Component {...props} />
  </React.Suspense>
)

const LazyLoadedPage = LazyPage(React.lazy(() => import('/path/to/component/'));

You can then call LazyLoadedPage in your tree as if it was a normal component.

CodePudding user response:

Yesterday I thought of a new way to phrase my question, and sure enough, there was the answer! I'll share what I ended up doing, and then explain how I got my answer.

It ended up being as simple as a wrapper for React.lazy():

const lazyWithPrefetch = (factory) => {
  factory();
  return lazy(factory);
};

This function takes as an argument a function wrapping a dynamic import, something like () => import("./Page1.js"). It then calls that function, triggering a network request, before returning a React.lazy() call. Because dynamic import is async, this doesn't block anything.


I got the inspiration for this from React.lazy and prefetching components. Yash Joshi's answer included this code block:

function lazyWithPreload(factory) {
  const Component = React.lazy(factory);
  Component.preload = factory;
  return Component;
}

const ComponentToPreload = lazyWithPreload(() => import("./Component"));

/* usage in Component */

ComponentToPreload.preload(); // this will trigger network request to load the component

Turning the prefetch into a method can be helpful, letting you tie it to a DOM event like a click or hover.

In my use case, I didn't need that sort of granularity, and two function calls per component (one to assign it and one to prefetch it) felt cluttered.

Also, this was a refactor of an existing project with a lot of imports, so I wanted to keep my changes minimal to avoid copying and pasting all morning. That's how I ended up putting the prefetch in the wrapper function.

  •  Tags:  
  • Related