Code splitting and data prefetch with React Suspense and Lazy Loading

Code splitting and data prefetch with React Suspense and Lazy Loading

In most cases, a react project will be comprised of multiple components that need to be imported into another and bundled by tools like Webpack, Browserify or Parcel. Importing a component is easily done like this:

import React from "react"
import OtherComponent from "./otherComponent"
import SomeOtherComponent from "./someOtherComponent"

function App() {
  return (
    <>
      <OtherComponent />
      <SomeOtherComponent />
    </>
  )
}
export default App

The downside to the above is that as the codebase grow, so do the bundle size and we end up bundling components that the use does not need yet or may not even need, leading to users seeing the website as slow due to longer Code Splitting

A solution to this problem is using Code Splitting which allows for dynamic import. React.Lazy allows rendering a dynamic import as a regular component.

The above code, then, becomes:

import React, { lazy } from "react"
const OtherComponent = React.lazy(() => import("./OtherComponent"))
const SomeOtherComponent = React.lazy(() => import("./SomeOtherComponent"))

function App() {
  return (
    <>
      <OtherComponent />
      <SomeOtherComponent />
    </>
  )
}
export default App

Next, we need to provide some kind of fallback to show while the component loads. Suspense allows for that, as demonstrated below:

import React, { Suspense, lazy } from "react"
const OtherComponent = React.lazy(() => import("./OtherComponent"))
const SomeOtherComponent = React.lazy(() => import("./SomeOtherComponent"))

function App() {
  return (
    <>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
        <SomeOtherComponent />
      </Suspense>
    </>
  )
}
export default App

Another downside to this is that large components may lead to the user waiting long after they click on a link, waiting for it to be loaded, hence, we can combine dynamic importing with lazy load. This allows for the chunks to load in the background before the user clicks on a page. The code can be modified as below:

import React, { Suspense, lazy } from "react"
const otherComponentPromise = import("./OtherComponent")
const OtherComponent = lazy(() => otherComponentPromise)
const someOtherComponentPromise = import("./SomeOtherComponent")
const SomeOtherComponent = lazy(() => someOtherComponentPromise)

function App() {
  return (
    <>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
        <SomeOtherComponent />
      </Suspense>
    </>
  )
}
export default App

The above code handles lazy loading and prefetching. However, in practice, fetching a undle in the background may fail, hence, we might want to retry a number of times before showing the error. We modify the code as below:

import React, { Suspense, lazy } from "react"

//function to retry lazy imports up to x times if it fails, before it errors out
const retry = (fn, retriesLeft = 5, interval = 1000) => {
  return new Promise((resolve, reject) => {
    fn()
      .then(resolve)
      .catch(error => {
        setTimeout(() => {
          if (retriesLeft === 1) {
            // reject('maximum retries exceeded');
            reject(error)
            return
          }

          // Passing on "reject" is the important part
          retry(fn, retriesLeft - 1, interval).then(resolve, reject)
        }, interval)
      })
  })
}

const otherComponentPromise = import("./OtherComponent")
const OtherComponent = lazy(() => retry(() => otherComponentPromise))
const someOtherComponentPromise = import("./SomeOtherComponent")
const SomeOtherComponent = lazy(() => retry(() => someOtherComponentPromise))

function App() {
  return (
    <>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
        <SomeOtherComponent />
      </Suspense>
    </>
  )
}
export default App

Finally, we’ve achieved proper code splitting, dynamic import and retry function. You should, also, have an error boundary component in case something goes wrong.


Recent Projects

Get The Latest Updates Delivered To Your Inbox

Subscribe to my newsletter and stay updated.