Skip to content

Instantly share code, notes, and snippets.

@acdlite
Last active January 20, 2023 08:23
Show Gist options
  • Save acdlite/a68433004f9d6b4cbc83b5cc3990c194 to your computer and use it in GitHub Desktop.
Save acdlite/a68433004f9d6b4cbc83b5cc3990c194 to your computer and use it in GitHub Desktop.
Quick and dirty code splitting with React Router v4
// getComponent is a function that returns a promise for a component
// It will not be called until the first mount
function asyncComponent(getComponent) {
return class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentWillMount() {
if (!this.state.Component) {
getComponent().then(Component => {
AsyncComponent.Component = Component
this.setState({ Component })
})
}
}
render() {
const { Component } = this.state
if (Component) {
return <Component {...this.props} />
}
return null
}
}
}
const Foo = asyncComponent(() =>
System.import('./Foo').then(module => module.default)
)
const Bar = asyncComponent(() =>
System.import('./Bar').then(module => module.default)
)
const App = () =>
<BrowserRouter>
<Link to="/foo">Foo</Link>
<Link to="/bar">Bar</Link>
<Match pattern="/foo" component={Foo} />
<Match pattern="/bar" component={Bar} />
</BrowserRouter>
export default App
@mrukas
Copy link

mrukas commented Apr 8, 2018

@nateq314 This should only be necessary if you're using something other than webpack. Using webpack the module only gets fetched one time, even if you're not caching the component in the static field.

How do I pass props to the lazy loaded component? I see that {...this.props} has been used, but you can't pass properties to the AsyncComponent. Am I missing something?

I'm currently using something like this:

export default function asyncComponent(getComponent, props = {}) {
    return class AsyncComponent extends Component {
        state = {};

        componentWillMount() {
            getComponent().then(({ default: component }) => {
                this.setState({ Component: component });
            });
        }

        render() {
            const { Component } = this.state
            return Component ? <Component {...props} /> : null;
        }
    };
}

The route entry looks like this:

 <Route path="/about" component={asyncRoute(() => import('./About'), { title: "About" })} />

@elycruz
Copy link

elycruz commented Apr 21, 2018

Nice!!!

I would use FetchedComponent or something internally instead of Component (since you're probably already importing that symbol (Component)).

Other than that awesome!

@49-22
Copy link

49-22 commented Nov 3, 2018

Hello everyone!

I'm using this code:

import React from "react";
import {
  BrowserRouter as Router,
  Route,
  Link,
  Switch,
  Redirect,
  Match
} from "react-router-dom";
// import Bundle from "Bundle/Bundle";

// import PedidoApp from "bundle-loader?lazy!./PedidoApp";
// import PedidoForm from "bundle-loader?lazy!./pedidoform";

// getComponent is a function that returns a promise for a component
// It will not be called until the first mount
const asyncComponent = getComponent => {
  return class AsyncComponent extends React.Component {
    static Component = null;
    state = { Component: AsyncComponent.Component };

    componentWillMount() {
      if (!this.state.Component) {
        getComponent().then(({ default: Component }) => {
          AsyncComponent.Component = Component;
          this.setState({ Component });
        });
      }
    }
    render() {
      const { Component } = this.state;
      if (Component) {
        return <Component {...this.props} />;
      }
      return null;
    }
  };
};

const PedidoApp = asyncComponent(() => import("./PedidoApp"));
const PedidoForm = asyncComponent(() => import("./pedidoform"));

class Root extends React.Component {
  render() {
    return (
      <Router>
        <div>
          <Link to="/pedido-1">Pedido</Link>
          <Link to="/pedido-1/add">Agregar</Link>

          <Route pattern="/pedido-1" component={PedidoApp} />
          <Route pattern="/pedido-1/add" component={PedidoForm} />
          <Redirect path="*" to="/pedido-1" />
        </div>
      </Router>
    );
  }
}

export default Root;

But it shows both components at the same time. How do I do in order to show <PedidoApp/> by default (Home) and when I click the Add button (or edit) show <PedidoForm/> ???

Thank you

You have to use Either <switch> component to wrap routes (or) Exact Attribute on <Route>

@StefanoSega
Copy link

Hello!

Writing an async component like this in TypeScript:

export function routeAsyncComponent(getComponent: () => Promise<React.Component>) {
  interface IRouteAsyncComponentState {
    Component: React.Component;
  }

  return class RouteAsyncComponent extends React.Component<any, IRouteAsyncComponentState> {
    static Component: React.Component = null;
    state = { Component: RouteAsyncComponent.Component };

    componentWillMount() {
      if (!this.state.Component) {
        getComponent().then(Component => {
          RouteAsyncComponent.Component = Component;
          this.setState({ Component });
        });
      }
    }
    render() {
      const { Component } = this.state;
      if (Component) {
        return <Component {...this.props} />;
      }

      return null;
    }
  }
}

TypeScript complains on return <Component {...this.props} />; :

Type assertion using the '<>' syntax is forbidden. Use the 'as' syntax instead. (no-angle-bracket-type-assertion)tslint(1)

Type assertion on object literals is forbidden, use a type annotation instead. (no-object-literal-type-assertion)tslint(1)

Cannot find name 'Component'.ts(2304)

@BertieGo
Copy link

so dirty and so good, bro

@lynda0214
Copy link

Thank you for this brilliant code. I've been using this code very usefully.

Since System.import is deprecated, you have to use import.

Additionally, rather than doing module => module.default for every routes, you can remove some unnecessary codes by doing ({default: Component}) => ... in componentWillMount

Full Code:

function asyncComponent(getComponent) {
  return class AsyncComponent extends React.Component {
    static Component = null;
    state = { Component: AsyncComponent.Component };

    componentWillMount() {
      if (!this.state.Component) {
        getComponent().then(({default: Component}) => {
          AsyncComponent.Component = Component
          this.setState({ Component })
        })
      }
    }
    render() {
      const { Component } = this.state
      if (Component) {
        return <Component {...this.props} />
      }
      return null
    }
  }
}

const Foo = asyncComponent(() => import('./Foo'))
const Bar = asyncComponent(() => import('./Bar'))

Can't figure out why .default is needed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment