React.lazy() and You

A Code Splitting Primer

About Me

Agenda

  • What is it?
  • When to use it?
  • When not to use it?
  • Usage examples.
  • Q&A

What is React.lazy()?

  • introduced in 16.6.0
  • simple wrapper for dynamic imports
  • abstracts away boilerplate
  • used with React.Suspense Component to provide fallback

What are dynamic imports?

  • Stage 3 (Candidate) in TC39 Process
  • functional import for JavaScript Modules
  • returns a Promise that resolves to JavaScript Module
  • facilitates developer-defined code splitting in build tools like webpack

What is code splitting?

  • built into webpack
  • breaking JS bundle into into smaller "chunks"
  • allows for smaller, more focused bundles
  • can be used to load code on demand, in parallel, based on priority, etc.

What's the point?

  • Lazy load expensive components
  • Only deliver the necessary code to the browser
  • Code splitting can provide novel approaches to code organization and architecture

But why?

  • Average website is ~2MB [1]
  • After images (and video), JavaScript is the biggest contributor to that bloat
  • ~40% of users abandon a website that takes longer than 3 seconds to load [2]

"Expensive" Components

  • Data Viz w/ heavy library like d3
  • Components that users rarely interact with
  • Components that are "below the fold"

Anything not in the Critical Path could be expensive.

Unnecessary Code

  • Code behind a feature flag
  • A/B tests that the user isn't experiencing
  • Routes the user will likely never visit

Anything the user doesn't need right now could be unnecessary. 

Code Splitting Approaches

  • Opt-in features
  • Related Routes
  • Similar Functionality
  • Team ownership

There are a ton of ways to split your code.

What's the catch?

React.Suspense does not support  Server Side Rendering.

 

Yet.

SSR Alternatives

Not every Component should be lazy loaded.

When not to lazy load.

  • Login Forms and other frequently interacted with Components
  • Whatever is most important on the page
  • Limit number of JS files per page to something sensible (Note: HTTP2 was supposed to be a panacea where we could make dozens to hundreds of requests on the same pipe and things would be great; that isn't necessarily the case in the real world.)

Lazy loading can prevent or delay user interaction.

Don't block your users.

  • Waiting to load something until the user tries to interact with it can produce a bad experience
  • You can leverage preloading and prerendering techniques to gain the benefits of code splitting without the drawbacks of preventing interaction
  • Consider if its possible to predict what a user will need and begin to load it just before they need it

Dynamic imports are not really supported in React Native.

Yet.

React Native

  • As far as I know, you're kind of out of luck on this one for now
  • It is supposed to be in the works

Can we see some examples?

I thought you'd never ask.

Let's go to the code.

import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => import('../lazy-component'));

export default () => (
  <main>
    <h1>Lazy Component Below</h1>
    <Suspense fallback={<p>Loading...</p>}>
      <LazyComponent />
    </Suspense>
  </main>
);

Basic Example

import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => /* webpackChunkName: "lazy" */ import('../lazy-component'));

export default () => (
  <main>
    <h1>Lazy Component Below</h1>
    <Suspense fallback={<p>Loading...</p>}>
      <LazyComponent />
    </Suspense>
  </main>
);

As a Named Chunk

Note: We're going to leave the import statement and variable declaration out out in some later slides due to space constraints.

export default class MyComponent extends Component {
  state = {
    showLazyComponent: false,
  };

  render() {
    const { showLazyComponent } = this.state;

    return (
      <main>
        <h1>Lazy Component Below</h1>

        <button
          type="button"
          onClick={() => this.setState({ showLazyComponent: !showLazyComponent })}
        >
          Toggle Lazy
        </button>

        {showLazyComponent &&
          <Suspense fallback={<p>Loading...</p>}>
            <LazyComponent />
          </Suspense>
        }
      </main>
    );
  }
}

Lazy Load on Click

import React, { Component, Supsense, lazy } from 'react';

// this will create a new bundle that gets loaded after the current bundle
const lazyComponentPromise = import(/* webpackChunkName: "lazy" */ '../lazy');

const LazyComponent = lazy(() => lazyComponentPromise);

export default () => (
  <main>
    <h1>Lazy Component Below</h1>
    <Suspense fallback={<p>Loading...</p>}>
      <LazyComponent />
    </Suspense>
  </main>
);

Code Splitting w/Preload

import { lazy } from 'react';

export default function lazyWithPreload(componentPromise) => {
  const Component = lazy(componentPromise);

  Component.preload = componentPromise;

  return Component; 
};

Developer Defined Preload

Note: We'll use this in the next slide.

import React, { Component, Suspense } from 'react';

import lazyWithPreload from '../lazy-with-preload';

const LazyComponent = lazyWithPreload(() => import('../lazy'));

export default class MyComponent extends Component {
  state = {
    showLazyComponent: false,
  };

  lazyLoad = () => {
    LazyComponent.preload();

    this.setState({ showLazyComponent: !this.state.showLazyComponent });
  };

  render() {
    const { showLazyComponent } = this.state;

    return (
      <main>
        <button type="button" onClick={this.lazyLoad}>Toggle Lazy</button>

        {this.state.showLazyComponent &&
          <Suspense fallback={<p>Loading...</p>}>
            <LazyComponent />
          </Suspense>
        }
      </main>
    );
  }
}

Have questions?

I might have answers.

React.lazy() and You

By Eric Allen

React.lazy() and You

Get to know React.lazy() and when you might want to leverage it. Demos are stored as git tags in this repository: https://github.com/ericrallen/dynamic-import-demo

  • 394