react-loadable
A higher order component for loading components with promises.
README

A higher order component for loading components with dynamic imports.
Install
- ```sh
- yarn add react-loadable
- ```
Example
- ``` js
- import Loadable from 'react-loadable';
- import Loading from './my-loading-component';
- const LoadableComponent = Loadable({
- loader: () => import('./my-component'),
- loading: Loading,
- });
- export default class App extends React.Component {
- render() {
- return <LoadableComponent/>;
- }
- }
- ```
Happy Customers:
Users
_If your company or project is using React Loadable, please open a PR and add
yourself to this list (in alphabetical order please)_
Also See:
Guide

Route-based splitting vs. Component-based splitting

Example: Maybe your app has a map buried inside of a tab component. Why
would you load a massive mapping library for the parent route every time when
the user may never go to that tab?
Introducing React Loadable
- ``` js
- import Bar from './components/Bar';
- class Foo extends React.Component {
- render() {
- return <Bar/>;
- }
- }
- ```
- ``` js
- class MyComponent extends React.Component {
- state = {
- Bar: null
- };
- componentWillMount() {
- import('./components/Bar').then(Bar => {
- this.setState({ Bar: Bar.default });
- });
- }
- render() {
- let {Bar} = this.state;
- if (!Bar) {
- return <div>Loading...</div>;
- } else {
- return <Bar/>;
- };
- }
- }
- ```
- ``` js
- import Loadable from 'react-loadable';
- const LoadableBar = Loadable({
- loader: () => import('./components/Bar'),
- loading() {
- return <div>Loading...</div>
- }
- });
- class MyComponent extends React.Component {
- render() {
- return <LoadableBar/>;
- }
- }
- ```
Automatic code-splitting on import()
Creating a great "Loading..." Component
- ``` js
- function Loading() {
- return <div>Loading...</div>;
- }
- Loadable({
- loader: () => import('./WillFailToLoad'), // oh no!
- loading: Loading,
- });
- ```
Loading error states
- ``` js
- function Loading(props) {
- if (props.error) {
- return <div>Error! <button onClick={ props.retry }>Retry</button>div>;
- } else {
- return <div>Loading...</div>;
- }
- }
- ```
Avoiding _Flash Of Loading Component_
- ``` js
- function Loading(props) {
- if (props.error) {
- return <div>Error! <button onClick={ props.retry }>Retry</button>div>;
- } else if (props.pastDelay) {
- return <div>Loading...</div>;
- } else {
- return null;
- }
- }
- ```
- ``` js
- Loadable({
- loader: () => import('./components/Bar'),
- loading: Loading,
- delay: 300, // 0.3 seconds
- });
- ```
Timing out when the loader is taking too long
- ``` js
- function Loading(props) {
- if (props.error) {
- return <div>Error! <button onClick={ props.retry }>Retry</button>div>;
- } else if (props.timedOut) {
- return <div>Taking a long time... <button onClick={ props.retry }>Retry</button>div>;
- } else if (props.pastDelay) {
- return <div>Loading...</div>;
- } else {
- return null;
- }
- }
- ```
- ``` js
- Loadable({
- loader: () => import('./components/Bar'),
- loading: Loading,
- timeout: 10000, // 10 seconds
- });
- ```
Customizing rendering
- ``` js
- Loadable({
- loader: () => import('./my-component'),
- render(loaded, props) {
- let Component = loaded.namedExport;
- return <Component {...props}/>;
- }
- });
- ```
Loading multiple resources
- ``` js
- 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}/>;
- },
- });
- ```
Preloading
- ``` js
- 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}>
- Show Bar
- </button>
- {this.state.showBar && <LoadableBar/>}
- </div>
- )
- }
- }
- ```
Server-Side Rendering
- ``` js
- import express from 'express';
- import React from 'react';
- import ReactDOMServer from 'react-dom/server';
- import App from './components/App';
- const app = express();
- app.get('/', (req, res) => {
- res.send(`
- doctype html>
- <html lang="en">
- <head>...</head>
- <body>
- <div id="app">${ReactDOMServer.renderToString(<App/>)}div>
- <script src="/dist/main.js"></script>
- </body>
- </html>
- `);
- });
- app.listen(3000, () => {
- console.log('Running on http://localhost:3000/');
- });
- ```
Preloading all your loadable components on the server
- ``` js
- Loadable.preloadAll().then(() => {
- app.listen(3000, () => {
- console.log('Running on http://localhost:3000/');
- });
- });
- ```
Picking up a server-side rendered app on the client
Declaring which modules are being loaded
- ``` js
- Loadable({
- loader: () => import('./Bar'),
- modules: ['./Bar'],
- webpack: () => [require.resolveWeak('./Bar')],
- });
- ```
- ``` json
- {
- "plugins": [
- "react-loadable/babel"
- ]
- }
- ```
Finding out which dynamic modules were rendered
- ``` js
- import Loadable from 'react-loadable';
- app.get('/', (req, res) => {
- let modules = [];
- let html = ReactDOMServer.renderToString(
- <Loadable.Capture report={moduleName => modules.push(moduleName)}>
- <App/>
- </Loadable.Capture>
- );
- console.log(modules);
- res.send(`...${html}...`);
- });
- ```
Mapping loaded modules to bundles
- ``` js
- // webpack.config.js
- import { ReactLoadablePlugin } from 'react-loadable/webpack';
- export default {
- plugins: [
- new ReactLoadablePlugin({
- filename: './dist/react-loadable.json',
- }),
- ],
- };
- ```
- ``` js
- import Loadable from 'react-loadable';
- import { getBundles } from 'react-loadable/webpack'
- import stats from './dist/react-loadable.json';
- app.get('/', (req, res) => {
- let modules = [];
- let html = ReactDOMServer.renderToString(
- <Loadable.Capture report={moduleName => modules.push(moduleName)}>
- <App/>
- </Loadable.Capture>
- );
- let bundles = getBundles(stats, modules);
- // ...
- });
- ```
Preloading ready loadable components on the client
- ``` js
- // src/entry.js
- import React from 'react';
- import ReactDOM from 'react-dom';
- import Loadable from 'react-loadable';
- import App from './components/App';
- window.main = () => {
- Loadable.preloadReady().then(() => {
- ReactDOM.hydrate(<App/>, document.getElementById('app'));
- });
- };
- ```
Now server-side rendering should work perfectly!
API Docs
Loadable
- ``` js
- const LoadableComponent = Loadable({
- loader: () => import('./Bar'),
- loading: Loading,
- delay: 200,
- timeout: 10000,
- });
- ```
Loadable.Map
- ``` js
- 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}/>;
- }
- });
- ```
Loadable and Loadable.Map Options
opts.loader
- ``` js
- Loadable({
- loader: () => import('./Bar'),
- });
- ```
- ``` js
- Loadable.Map({
- loader: {
- Bar: () => import('./Bar'),
- i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
- },
- });
- ```
opts.loading
- ``` js
- Loadable({
- loading: LoadingComponent,
- });
- ```
- ``` js
- Loadable({
- loading: () => null,
- });
- ```
opts.delay
- ``` js
- Loadable({
- delay: 200
- });
- ```
opts.timeout
- ``` js
- Loadable({
- timeout: 10000
- });
- ```
opts.render
- ``` js
- Loadable({
- render(loaded, props) {
- let Component = loaded.default;
- return <Component {...props}/>;
- }
- });
- ```
opts.webpack
- ``` js
- Loadable({
- loader: () => import('./Foo'),
- webpack: () => [require.resolveWeak('./Foo')],
- });
- ```
opts.modules
- ``` js
- Loadable({
- loader: () => import('./my-component'),
- modules: ['./my-component'],
- });
- ```
LoadableComponent
- ``` js
- const LoadableComponent = Loadable({
- // ...
- });
- ```
LoadableComponent.preload()
- ``` js
- const LoadableComponent = Loadable({...});
- LoadableComponent.preload();
- ```
LoadingComponent
- ``` js
- function LoadingComponent(props) {
- if (props.error) {
- // When the loader has errored
- return <div>Error! <button onClick={ props.retry }>Retry</button>div>;
- } else if (props.timedOut) {
- // When the loader has taken longer than the timeout
- return <div>Taking a long time... <button onClick={ props.retry }>Retry</button>div>;
- } else if (props.pastDelay) {
- // When the loader has taken longer than the delay
- return <div>Loading...</div>;
- } else {
- // When the loader has just started
- return null;
- }
- }
- Loadable({
- loading: LoadingComponent,
- });
- ```
props.error
- ``` js
- function LoadingComponent(props) {
- if (props.error) {
- return <div>Error!</div>;
- } else {
- return <div>Loading...</div>;
- }
- }
- ```
props.retry
- ``` js
- function LoadingComponent(props) {
- if (props.error) {
- return <div>Error! <button onClick={ props.retry }>Retry</button>div>;
- } else {
- return <div>Loading...</div>;
- }
- }
- ```
props.timedOut
- ``` js
- function LoadingComponent(props) {
- if (props.timedOut) {
- return <div>Taking a long time...</div>;
- } else {
- return <div>Loading...</div>;
- }
- }
- ```
props.pastDelay
- ``` js
- function LoadingComponent(props) {
- if (props.pastDelay) {
- return <div>Loading...</div>;
- } else {
- return null;
- }
- }
- ```
Loadable.preloadAll()
- ``` js
- Loadable.preloadAll().then(() => {
- app.listen(3000, () => {
- console.log('Running on http://localhost:3000/');
- });
- });
- ```
- ``` js
- // During module initialization...
- const LoadableComponent = Loadable({...});
- class MyComponent extends React.Component {
- componentDidMount() {
- // ...
- }
- }
- ```
- ``` js
- // ...
- class MyComponent extends React.Component {
- componentDidMount() {
- // During app render...
- const LoadableComponent = Loadable({...});
- }
- }
- ```
Note: Loadable.preloadAll() will not work if you have more than one
copy of react-loadable in your app.
Loadable.preloadReady()
- ``` js
- Loadable.preloadReady().then(() => {
- ReactDOM.hydrate(<App/>, document.getElementById('app'));
- });
- ```
Loadable.Capture
- ``` js
- let modules = [];
- let html = ReactDOMServer.renderToString(
- <Loadable.Capture report={moduleName => modules.push(moduleName)}>
- <App/>
- </Loadable.Capture>
- );
- console.log(modules);
- ```
Babel Plugin
- ``` json
- {
- "plugins": ["react-loadable/babel"]
- }
- ```
- ``` js
- import Loadable from 'react-loadable';
- const LoadableMyComponent = Loadable({
- loader: () => import('./MyComponent'),
- });
- const LoadableComponents = Loadable.Map({
- loader: {
- One: () => import('./One'),
- Two: () => import('./Two'),
- },
- });
- ```
- ``` js
- import Loadable from 'react-loadable';
- import path from 'path';
- const LoadableMyComponent = Loadable({
- loader: () => import('./MyComponent'),
- webpack: () => [require.resolveWeak('./MyComponent')],
- modules: [path.join(__dirname, './MyComponent')],
- });
- const LoadableComponents = Loadable.Map({
- loader: {
- One: () => import('./One'),
- Two: () => import('./Two'),
- },
- webpack: () => [require.resolveWeak('./One'), require.resolveWeak('./Two')],
- modules: [path.join(__dirname, './One'), path.join(__dirname, './Two')],
- });
- ```
Webpack Plugin
- ``` js
- // webpack.config.js
- import { ReactLoadablePlugin } from 'react-loadable/webpack';
- export default {
- plugins: [
- new ReactLoadablePlugin({
- filename: './dist/react-loadable.json',
- }),
- ],
- };
- ```
getBundles
- ``` js
- import { getBundles } from 'react-loadable/webpack';
- let bundles = getBundles(stats, modules);
- ```
FAQ
How do I avoid repetition?
- ``` js
- import Loadable from 'react-loadable';
- import Loading from './my-loading-component';
- export default function MyLoadable(opts) {
- return Loadable(Object.assign({
- loading: Loading,
- delay: 200,
- timeout: 10000,
- }, opts));
- };
- ```
- ``` js
- import MyLoadable from './MyLoadable';
- const LoadableMyComponent = MyLoadable({
- loader: () => import('./MyComponent'),
- });
- export default class App extends React.Component {
- render() {
- return <LoadableMyComponent/>;
- }
- }
- ```
- ``` js
- import MyLoadable from './MyLoadable';
- const LoadableMyComponent = MyLoadable({
- loader: () => import('./MyComponent'),
- modules: ['./MyComponent'],
- webpack: () => [require.resolveWeak('./MyComponent')],
- });
- export default class App extends React.Component {
- render() {
- return <LoadableMyComponent/>;
- }
- }
- ```
How do I handle other styles .css or sourcemaps .map with server-side rendering?
- ``` js
- let bundles = getBundles(stats, modules);
- let styles = bundles.filter(bundle => bundle.file.endsWith('.css'));
- let scripts = bundles.filter(bundle => bundle.file.endsWith('.js'));
- res.send(`
- doctype html>
- <html lang="en">
- <head>
- ...
- ${styles.map(style => {
- return `<link href="/dist/${style.file}" rel="stylesheet"/>`
- }).join('\n')}
- </head>
- <body>
- <div id="app">${html}</div>
- <script src="/dist/main.js"></script>
- ${scripts.map(script => {
- return `<script src="/dist/${script.file}"></script>`
- }).join('\n')}
- </body>
- </html>
- `);
- ```