react-lodable is a higher order component for loading components with dynamic
imports.

Code-splitting is the process of taking one large bundle containing your entire
app, and splitting them up into multiple smaller bundles which contain seperate
parts of your app.

This might seem difficult to do, but tools like Webpack have this built in, and
React Loadable is designed to make is super simple.

Route-based splitting vs. Component-based splitting

A common piece of advice you will see is to break your app into seperate routes
and load each one asynchronously. This seems to work well enough for many apps
– a a user, clicking a link and waiting for a page to load is a familiar
experience on the web.

Namely, a route is simple a component.

But in fact there are more places than just routes where you can pretty easily
split apart your app: Modals, Tabs, and many more UI Components hide content
until the user has done something to reveal it.

Example: Maybe your app ahs a map buried inside of a tab component. Why would
you load a massive mapping library for the parent route every time the user
never to to that tab.

React Loadable is a small library that makes component-centric code splitting
incredibly easy in React.

Loadable is a higher-order component(a function that returns a component)
which lets you dynamically load any module before rendering it into your app.

We can make it by dynamic import

1
2
3
4
5
6
7
import Bar from './components/Bar'

class Foo extends React.Component {
render() {
return <Bar />
}
}

=>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Foo extends React.Component {
state = {
Bar: null,
}
componentWillMount() {
import('./components/Bar').then(Bar => this.setState({ Bar }))
}
render() {
let { Bar } = this.state
if (!Bar) {
return <div>Loading...</div>
} else {
return <Bar />
}
}
}

But that’s whole bunch of work, and it doesn’t even handle a bunch of caess.
What about when import() fails? What about server-side rendering?

react-loadable considers the unexpected cases.

1
2
3
4
5
6
7
8
9
10
11
12
import Loadable from 'react-loadable'

const LoadableBar = Loadable({
loader: () => import('./components/Bar'),
loading: () => <div>Loading</div>,
})

class Foo extends React.Component {
render() {
return <LoadableBar />
}
}

When you use import() with webpack2, it will
automatically code-split for
you with no additional configuration.

Define your Loading Component

1
2
3
function Loading() {
return <div>Loading</div>
}

When your loader fails, your Loading component will receive an error prop which
will be true(otherwise it will be false).

1
2
3
4
5
6
function Loading(props) {
if (props.error) {
return <div>Error</div>
}
return <div>Loading...</div>
}

Sometimes components load really quickly(<200ms) and the loading screen only
quickly flashes on the screen.

Your loading component will get a pastDelay props which will only be true
once the component has taken longer to load than a set delay.

1
2
3
4
5
6
7
8
9
10
function Loading(props) {
if (props.error) {
return <div>Error</div>
} else if (props.pastDelay) {
// only show loading when it takes longer than 200ms
return <div>Loading</div>
} else {
return null
}
}

This delay defaults to 200ms, but you can customize the delay in Loadable.

1
2
3
4
5
Loadable({
loader: () => import('./components/Bar'),
loading: () => Loading,
delay: 300,
})

Timing out when the loader is taking too long

Sometimes network connections suck and never resolve or fail, they just hang
there forever. This sucks for the user because they won’t know if it should
always take this long, or if they should try refreshing.

The Loading component will receive a timeOut prop which will be set to true
when the loader has timed out.

1
2
3
4
5
6
7
8
9
10
11
function Loading(props) {
if (props.error) {
return <div>Error</div>
} else if (props.timedOut) {
return <div>Timed out</div>
} else if (props.pastDelay) {
return <div>Loading</div>
} else {
return null
}
}

This feature is disabled by default, you can pass a timeout option to
Loadable to enable it.

By default Loadable will render the default export of the returned module.
If you want to customize this behavior you can use the render option.

1
2
3
4
5
6
7
Loadable({
loader: () => import('./myComponent'),
render(loaded, props) {
let Component = loaded.namedExport
return <Component {...props} />
},
})

You can do whatever you want within loader() as long as it returns a promise
and you are able to render everything

You can load multiple resources in parallel with Loadable.Map

1
2
3
4
5
6
7
8
9
10
11
Loadable.Map({
loader: {
Bar: () => import('./Bar'),
i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
},
render(loaded, props) {
let Bar = loaded.Bar.default
let i18n = loaded.i18n
return <Bar {...props} i18n={i18n} />
},
})

As an optimization, you can also decide to preload a component before it gets
rendered.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const LoadableBar = Loadable({
loader: () => import('./Bar'),
loading: Loading,
})

class MyComponent extends React.Component {
state = {
showBar: false,
}

onClick = () => {
this.setState({ showBar: true })
}

onMouseOver = () => {
LoadableBar.preload()
}

render() {
return (
<div>
<button onClick={this.onClick} onMouseOver={this.onMouseOver}>
showbar
</button>
{this.state.showBar && <LoadableBar />}
</div>
)
}
}

Server-side rendering to see Github