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
@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