Giter Club home page Giter Club logo

react-async-component's Introduction

react-async-component πŸ“¬

Resolve components asynchronously, with support for code splitting and advanced server side rendering use cases.

npm MIT License Travis Codecov

const AsyncProduct = asyncComponent({
  resolve: () => System.import('./Product'),
  LoadingComponent: ({ productId }) => <div>Loading {productId}</div>, // Optional
  ErrorComponent: ({ error }) => <div>{error.message}</div> // Optional
});

<AsyncProduct productId={1} /> // πŸš€

TOCs

Introduction

This library does not require that you use either Webpack or Babel. Instead it provides you a generic and "pure" Javascript/React API which allows for the expression of lazy-loaded Components. It's Promise-based API naturally allows you to take advantage of modern code splitting APIs (e.g import(), System.import, require.ensure).

Features

  • Supports any major code splitting API.
  • Show a LoadingComponent until your component is resolved.
  • Show an ErrorComponent if your component resolution fails.
  • Prevents flash-of-content by tracking already resolved Components.
  • Full server side rendering support, allowing client side state rehydration, avoiding React checksum errors.

Usage

Imagine you had the following Product component:

export default function Product({ id }) {
  return <div>Product {id}</div>
}

To make this asynchronous create a new file that wraps it with asyncComponent, like so:

import { asyncComponent } from 'react-async-component';

export default asyncComponent({
  resolve: () => System.import('./Product')
});

I recommend that you use the following folder/file structure:

 |- components
    |- AsyncProduct
       |- index.js   // contains asyncComponent
       |- Product.js // The component you want resolved asynchronously

Now, you can simply import AsyncProduct anywhere in your application and use it exactly as you would any other component.

For example:

import AsyncProduct from './components/AsyncProduct'

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to My App</h1>
      <AsyncProduct id={1337} />
    </div>
  )
}

API

asyncComponent(config)

The asynchronous component factory. Config goes in, an asynchronous component comes out.

Arguments

  • config (Object) : The configuration object for the async Component. It has the following properties available:
    • resolve (() => Promise) : A function that should return a Promise that will resolve the Component you wish to be async.
    • LoadingComponent (Component, Optional, default: null) : A Component that will be displayed until your async Component is resolved. All props will be passed to it.
    • ErrorComponent (Component, Optional, default: null) : A Component that will be displayed if any error occurred whilst trying to resolve your component. All props will be passed to it as well as an error prop containing the Error.
    • name (String, Optional, default: 'AsyncComponent') : Use this if you would like to name the created async Component, which helps when firing up the React Dev Tools for example.
    • autoResolveES2015Default (Boolean, Optional, default: true) : Especially useful if you are resolving ES2015 modules. The resolved module will be checked to see if it has a .default and if so then the value attached to .default will be used. So easy to forget to do that. πŸ˜€
    • env (String, Optional) : Provide either 'node' or 'browser' so you can write your own environment detection. Especially useful when using PhantomJS or ElectronJS to prerender the React App.
    • serverMode (Boolean, Optional, default: 'resolve') : Only applies for server side rendering applications. Please see the documentation on server side rendering. The following values are allowed.
      • 'resolve' - Your asynchronous component will be resolved and rendered on the server. It's children will be checked to see if there are any nested asynchronous component instances, which will then be processed based on the serverMode value that was associated with them.
      • 'defer' - Your asynchronous component will not be rendered on the server, instead deferring rendering to the client/browser.
      • 'boundary' - Your asynchronous component will be resolved and rendered on the server. However, if it has a nested asynchronous component instance within it's children that component will be ignored and treated as being deferred for rendering in the client/browser instead (it's serverMode will be ignored). We highly recommend that you consider using defer as much as you can.

Returns

A React Component.

Examples

LoadingComponent
export default asyncComponent({
  resolve: () => import('./Product'),
  LoadingComponent: ({ id }) => <div>Loading product {id}</div>
})
ErrorComponent
export default asyncComponent({
  resolve: () => import('./Product'),
  ErrorComponent: ({ error }) => <div>{error.message}</div>
})
Named chunks
export default asyncComponent({
  resolve: () => new Promise(resolve =>
    // Webpack's code splitting API w/naming
    require.ensure(
      [],
      (require) => {
        resolve(require('./Product'));
      },
      'ChunkName'
    )
  )
})

<AsyncComponentProvider />

Currently only useful when building server side rendering applications. Wraps your application allowing for efficient and effective use of asynchronous components.

Props

  • asyncContext (Object) : Used so that you can gain hooks into the context for server side rendering render tracking and rehydration. See the createAsyncContext helper for creating an instance.
  • rehydrateState (Object, Optional) : Used on the "browser-side" of a server side rendering application (see the docs). This allows you to provide the state returned by the server to be used to rehydrate the client appropriately.

createAsyncContext()

Creates an asynchronous context for use by the <AsyncComponentProvider />. The context is an object that exposes the following properties to you:

  • getState() (() => Object) : A function that when executed gets the current state of the <AsyncComponentProvider />. i.e. which async components resolved / failed to resolve etc. This is especially useful for server sider rendering applications where you need to provide the server rendered state to the client instance in order to ensure the required asynchronous component instances are resolved prior to render.

Server Side Rendering

NOTE: This section only really applies if you would like to have control over the behaviour of how your asyncComponent instances are rendered on the server. If you don't mind your asyncComponents always being resolved on the client only then you need not do any of the below. In my opinion there is great value in just server rendering your app shell and having everything else resolve on the client, however, you may have very strict SEO needs - in which case, we have your back.

This library has been designed for interoperability with react-async-bootstrapper.

react-async-bootstrapper allows us to do a "pre-render parse" of our React Element tree and execute an asyncBootstrap function that are attached to a components within the tree. In our case the "bootstrapping" process involves the resolution of asynchronous components so that they can be rendered "synchronously" by the server. We use this 3rd party library as it allows interoperability with other libraries which also require a "bootstrapping" process (e.g. data preloading as supported by react-jobs).

Firstly, install react-async-bootstrapper:

npm install react-async-bootstrapper

Now, let's configure the "server" side. You could use a similar express (or other HTTP server) middleware configuration:

import React from 'react'
import { renderToString } from 'react-dom/server'
import { AsyncComponentProvider, createAsyncContext } from 'react-async-component' // πŸ‘ˆ
import asyncBootstrapper from 'react-async-bootstrapper' // πŸ‘ˆ
import serialize from 'serialize-javascript'

import MyApp from './shared/components/MyApp'

export default function expressMiddleware(req, res, next) {
  //    Create the async context for our provider, this grants
  // πŸ‘‡ us the ability to tap into the state to send back to the client.
  const asyncContext = createAsyncContext()

  // πŸ‘‡ Ensure you wrap your application with the provider.
  const app = (
    <AsyncComponentProvider asyncContext={asyncContext}>
      <MyApp />
    </AsyncComponentProvider>
  )

  // πŸ‘‡ This makes sure we "bootstrap" resolve any async components prior to rendering
  asyncBootstrapper(app).then(() => {
      // We can now render our app πŸ‘‡
      const appString = renderToString(app)

      // Get the async component state. πŸ‘‡
      const asyncState = asyncContext.getState()

      const html = `
        <html>
          <head>
            <title>Example</title>
          </head>
          <body>
            <div id="app">${appString}</div>
            <script type="text/javascript">
              // Serialise the state into the HTML response πŸ‘‡
              window.ASYNC_COMPONENTS_STATE = ${serialize(asyncState)}
            </script>
          </body>
        </html>`

      res.send(html)
    })
}

Then on the "client" side you would do the following:

import React from 'react'
import { render } from 'react-dom'
import { AsyncComponentProvider, createAsyncContext } from 'react-async-component' // πŸ‘ˆ
import asyncBootstrapper from 'react-async-bootstrapper' // πŸ‘ˆ
import MyApp from './components/MyApp'

// πŸ‘‡ Get any "rehydrate" state sent back by the server
const rehydrateState = window.ASYNC_COMPONENTS_STATE

//   Ensure you wrap your application with the provider,
// πŸ‘‡ and pass in the rehydrateState.
const app = (
  <AsyncComponentProvider  rehydrateState={rehydrateState}>
    <MyApp />
  </AsyncComponentProvider>
)

//   We run the bootstrapper again, which in this context will
//   ensure that all components specified by the rehydrateState
// πŸ‘‡ will be resolved prior to render.
asyncBootstrapper(app).then(() => {
  // πŸ‘‡ Render the app
  render(app, document.getElementById('app'))
});

SSR AsyncComponent Resolution Process

It is worth us highlighting exactly how we go about resolving and rendering your asyncComponent instances on the server. This knowledge will help you become aware of potential issues with your component implementations as well as how to effectively use our provided configuration properties to create more efficient implementations.

When running react-async-bootstrapper on the server the helper has to walk through your react element tree (depth first i.e. top down) in order to discover all the asyncComponent instances and resolve them in preparation for when you call the ReactDOM.renderToString. As it walks through the tree it has to call the componentWillMount method on your Components and then the render methods so that it can get back the child react elements for each Component and continue walking down the element tree. When it discovers an asyncComponent instance it will first resolve the Component that it refers to and then it will continue walking down it's child elements (unless you set the configuration for your asyncComponent to not allow this) in order to try and discover any nested asyncComponent instances. It continues doing this until it exhausts your element tree.

Although this operation isn't as expensive as an actual render as we don't generate the DOM it can still be quite wasteful if you have a deep tree. Therefore we have provided a set of configuration values that allow you to massively optimise this process. See the next section below.

SSR Performance Optimisation

As discussed in the "SSR AsyncComponent Resolution Process" section above it is possible to have an inefficient implementation of your asyncComponent instances. Therefore we introduced a new configuration object property for the asyncComponent factory, called serverMode, which provides you with a mechanism to optimise the configuration of your async Component instances. Please see the API documentation for more information.

Understand your own applications needs and use the options appropriately . I personally recommend using mostly "defer" and a bit of "boundary". Try to see code splitting as allowing you to server side render an application shell to give the user perceived performance. Of course there will be requirements otherwise (SEO), but try to isolate these components and use a "boundary" as soon as you feel you can.

Demo

You can see a "live" version here. This is a deployment of the "React, Universally" starter kit that makes use of this library. Open the network tab and then click the menu items to see the asynchronous component resolving in action.

FAQs

Let me know if you have any...

react-async-component's People

Contributors

birkir avatar ctrlplusb avatar fabianishere avatar jounqin avatar tadeegan avatar threepointone avatar timsuchanek avatar xzyfer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-async-component's Issues

Issues with async resolve

I tried out the async resolve on a React Universally project and I ran into a lot of nasty little issues that were hard to debug. I'm still not sure what exactly was going wrong. And I would like to find out how to avoid this

The idea was basically to reduce the size of the initial loaded component and take advantage of code splitting by using

export default asyncComponent({
  // include home and about route in same chunk e.g main
  resolve: () =>
    new Promise(resolve =>
      require.ensure(
        [],
        (require) => {
          resolve(require('./AdminRoute'));
        },
        'admin',
      ),
    ),
  LoadingComponent: () => <Loading />,
});

instead of

export default asyncComponent({
  resolve: () => System.import('./AdminRoute'),
  LoadingComponent: () => <Loading />,
});

However, I ran into issues like loading a location and then navigating to a another location and getting errors like this:

screen shot 2017-09-12 at 08 25 43

which translates to:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.

This only happened on production.

I initially thought this was due to caching since we are using CloudFlare on the front end, but I added some rules there to prevent caching of /index.html, and I still had the issues.

So, in the end I resorted to going back to big fat deployment file since the pressure was on to deliver. But I would really like to understand how to deal with chunking properly so these errors don't occur and if they do are easier to debug.

Document the "multiple componentWillMount" behaviour

Hey, thanks for your work on this.

I use componentWillMount to kick off some redux-thunks that do data fetching for my component on the server side.

Since react-async-component walks the tree to find the async components, it triggers all of the componentWillMount calls...then I still have to render the resulting appWithAsyncComponents with react-dom that invokes all of those componentWillMounts again.

The result is that I end up hitting my API twice for everything...once while finding the async components, and once when actually rendering to string.

UPDATE: I also find when using react-resolver that the same occurs. Essentially, any data fetching / expensive activity that's triggered by a pass over the tree would have to happen twice with this sort of approach.

Server side rendering with fetched data

Hi! Thank you for a nice library!

I'm trying to saddle server-side rendering for my app so I'm wondering...
Is it possible to use react-async-component to load all the data which is fetching in componentWillMount method at the server and then return the fully loaded component to the client? So instead of preloaders the user will be able to see a page with some loaded data.

I'm trying to implement such example, but I'm basically having 2 times of calling render and componentWillMount method without waiting for promises.

Support dynamic imports and arbitrary modules, not just components

Webpack supports dynamic imports, which can have a variable in the module path.
To support this with react-async-component it must hold more than one loaded module, not just one.
This way it can prevent flash of content for each already loaded module.

Example for this is a bunch of possible icon files to load, but only know which to render at runtime.
In combination of that example and the dynamic import, another feature would be to use the loaded module not only as component.
I used another method render to do something with the loaded module.

Here is an exapmle of the icon use case:

import React from 'react';
import GeneralIcon from 'place/GeneralIcon'
import { asyncComponent } from 'react-async-component';

export let config = {
    resolve: (props) => import(`icons/${props.iconName}/iconDescription`),
    getModuleId: (props) => props.iconName,
    autoResolveES2015Default: false,
    render: (iconDescription, props) => <GeneralIcon {...props} iconDescription={ iconDescription } />,
};

export default asyncComponent(config);

This can be used to

  • apply module content to a generic component
  • bundling each file in a directory as its own bundle and decide at runtime by props which to show
  • loading one module which contain different parts of the app, like @djeeg showed here

I created a working example, you can see the changes needed here.
@ctrlplusb please tell me if this looks good to you and I should proceed with it. I don't know much about SSR, so this is better done by you, if needs additional changes.

setState Errors

Getting the always loved Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the AsyncComponent component.

I noticed when perusing the code there was no handling of componentWillUnmount to cancel your promises so that they don't request this.setState().

LoadingComponent progress?

Hello. Can I get current fetching progress of component?
Example: XMLHttpRequest has event 'progress'. Size of my async component is 500kb. I want see progress bar on top of the page with progress value in LoadingComponent

Preload dependencies during idle time?

Hey, cool to see you have abstracted away the lazy loading handling for large React tree.
I found the approach much better than what react-router is suggesting to do!
I'm wondering, do you have any plan preloading dependencies on the client side during the idle time?

I have been implemented a naive solution, I'm pretty sure we could improve that. It's preloading everything.
If we were collecting some data. For instance, the order of lazing loading, we could even build a prediction model. I have heard that Facebook is using a machine learning algorithm to smartly load dependencies ahead of time.

Async component local state not hydrated

Hello,

First, thank you for your great work ! I daily use few of your packages (async-boostrapper, react-universally etc)

I have an issue with react-async-component with SSR, which does not rehydrate components local states. Is this a feature this package is supposed to offer, or did I misunderstood allowing client side state rehydration, avoiding React checksum errors. ?

I have no simple example to submit (SSR+CodeSplit is not simple :p) but this can be reproduced with react-universally in a few steps :

  • Clone react-universally (master)
  • Edit shared/components/DemoApp/AsyncCounterRoute/CounterRoute.js
  • Add a componentWillMount() which modify state (not predictable) like :
componentWillMount() {
        this.setState({counter: Math.random()});
  }

--> State is not rehydrated, react checksum errors appears etc.

Thank you !

Optimisations

I need to do an optimisation run over the tree walk algorithm. Some clever tracking will allow us to do early bails and avoid unnecessary walks.

Error Handling

I think you will need to do some extra error handling here. This is a bug in my code I believe but react reports it as an error of AsyncComponent which will make debugging difficult:

invariant.js:44 Uncaught (in promise) Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of `AsyncComponent`.

HMR works Once then "is not accepted"

Hey man,
So I am having an issue that is killing me. I am not sure if its related to async component or what - i know there are generally issues with react-router and HMR and adding the async is just the cherry on top :-P

Anyway I see you've dealt with literally every issue I am running into so I gotta ask!

On my first update of an async route it does update but I get a warning:

main.js:107 [HMR] unexpected require(./ui/screens/Home/Home.js) from disposed module ./ui/routes.js

then on the next update it fails saying the given path is not accepted.

Any ideas in general? I can provide more information of course but I am pretty sure I'm just doing whatever you were when you had the issues.

How to call async component's internal function

I'm having a little problem.
Say I have an async component that is encapsulated with react-async-component, like this:

const AsyncComponent = asyncComponent({
  resolve: () => new Promise(resolve =>
    require.ensure([], require => {
      resolve(require('draft-js'))
    }
  )
})

then call its internal functions by refs

<AsyncComponent ref={c => this.editor = c} />

this.editor.focus();

but will get Uncaught TypeError: _this.editor.focus is not a function error, and I use console.log(this.editor) see it's a async component and has not focus function so that I can't access it.

I spent a lot of time but still have no idea how to fix it,
does anyone can help ? thanks πŸ˜„

TypeScript & ES2015 Examples

First, this library is awesome!

Second, I want to show off my TypeScript and ES2015 examples using react-async-component. I also have react-hot-loader working with it. It took me a long time to get everything figured out so I hope it helps others.

Features are TypeScript or ES2015, Universal (SSR), React Hot Loader 3, React Router 4, Redux, Redux Saga, Redux Form, Async Component Code Splitting, Hapi, Webpack 3.

[Discussion] Advanced use case

Trying to convert react-universally to use this now while I build up the routes management structure and having issues.. .my app is mounting twice, it runs completely and renders then withAsyncComponents forces it to launch again and it causes all kinds of havoc.

const RenderedApp = ({ App, store }) => (
  <ReduxProvider store={store}>
    <BrowserRouter>
      <App store={store} />
    </BrowserRouter>
  </ReduxProvider>
)

function renderApp(store) {
  /* Get our Rendered App & Redux Store */
  // πŸ‘‡ run helper on your app and get back a result object.
  //    ❗️ The result includes a decorated version of your app
  //    that will allow your application to use async components
  //    in an efficient manner.
  withAsyncComponents(<RenderedApp App={App} store={store} />).then(
    ({ appWithAsyncComponents }) => {
      console.log('With Async', appWithAsyncComponents)
      render(appWithAsyncComponents, container)
    }
  )
}

image

As you can see it calls the With Async log which renders our app however it has already finished rendering the app in the first place when this is called.

Async component won't hot-reload

I moved my app from code-split-component to react-async-component but the Async components don't seem to hot reload properly. When I make a change to an Async component I get the usual messages in Chrome console with no differences ([HMR].. ) however the displayed content doesn't change. If I change an Async component then also change a non-async component, the changes to both appear at the same time.

Relevant section of App.jsx:

import { withAsyncComponents } from 'react-async-component'

import store from './store'
import Root from './Root'

function renderApp(Param) {
  const app = (
    <AppContainer>
      <Provider store={store}>
        <Param />
      </Provider>
    </AppContainer>
  )

  withAsyncComponents(app).then(({ appWithAsyncComponents }) =>
    ReactDOM.render(appWithAsyncComponents, rootElement), // eslint-disable-line
  )
}

renderApp(Root)

if (module.hot) {
  module.hot.accept(
    './Root',
    () => renderApp(require('./Root').default), //eslint-disable-line
  )
}

AppContent.jsx:

import IntroductionAsync from './IntroductionAsync'

<Match pattern="/" exactly component={IntroductionAsync} />

IntroductionAsync.jsx:

import { createAsyncComponent } from 'react-async-component'

const AsyncIntroduction = createAsyncComponent({
  resolve: () => System.import('./Introduction'),
})

export default AsyncIntroduction

The problem occurs when I try to edit Introduction.jsx.

[email protected]
[email protected]
[email protected]

Is there something I'm doing wrong or is this a bug of some sort?

Pre resolve an asyncComponent when another component resolves

Thanks for building this package, it was simple to implement.
I'm looking for suggestions on the best way to resolve an async component from another async component. I basically want to preload another component path after another resolves. This is just an optimization to remove the loading on a path users are likely to take next. Since they are just components, I guess I could pre initialize the next component in componentDidMount of the resolved component. Any other ideas?

It might be nice to have a built in mechanism for this like:

asyncComponent({
  ...
  thenResolve: [AnotherAsyncComp, AnotherAsyncComp2]
})

Possible Issues with withAsyncComponent

Thanks for putting this together.

Line 64

 if (rehydrateState) {
          if (!rehydrateState.resolved[id]) {
            return false;
          }
....

Not sure what rehydrateState.resolved[id] is referring to, but in the case of rehydrateState.resolved not defined it throws an error which I guess you should guard against its null-ability.

Line 103

 state: { resolved: execContext.getResolved() },

I think you kill state rehydration too soon before it gets to the client.
Currently to get around this I only requery store.getState() which is not ideal.

Investigate possible solutions to React Hot Loader compatability

Simplification looks really nice. What would be great is to add old disable flag from code-split.
This will help to switch between hot reload or async-component in runtime/build.
Something like

// client-entry.js 

import { withAsyncComponents } from 'react-async-component'; // πŸ‘ˆ

const app = <MyApp />;

//       adding disabled based on hot reload env var  πŸ‘‡
const render = (app) => withAsyncComponents(app, process.env.HOT_RELOAD)
.then((result) => {
    const {
      appWithAsyncComponents: Root
    } = result;
   ReactDOM.render(
      <ReactHotLoader>
        <Provider store={store}>
              <Router><Root/></Router>
        </Provider>
      </ReactHotLoader>,
      document.getElementById('app'));
  });

// Hot reloading on the client
if (process.env.NODE_ENV === 'development' && module.hot) {
  module.hot.accept('./app/routing/Root', () => {
    const Root = require('./app/routing/Root').default; // eslint-disable-line no-shadow

    render(Root);
  });
}

const Root = require('./app/routing/Root').default; // eslint-disable-line no-shadow
render(Root);

Similar on server side if needed

withAsyncComponents(app, process.env.HOT_RELOAD)
    //        πŸ‘‡ and you get back a result object.
    .then((result) => {
      const {
        // ❗️ The result includes a decorated version of your app
        // that will have the async components initialised for
        // the renderToString call.
        appWithAsyncComponents,
        // This state object represents the async components that
        // were rendered by the server. We will need to send
        // this back to the client, attaching it to the window
        // object so that the client can rehydrate the application
        // to the expected state and avoid React checksum issues.
        state,
        // This is the identifier you should use when attaching
        // the state to the "window" object.
        STATE_IDENTIFIER
      } = result;

      const appString = renderToString(appWithAsyncComponents);

What do you think?
I can see great value if having ability to run hot reload for rapid dev of something during development (even if it not really working that great) and being able to switch to code split when needed - like production or debugging of something

Context is empty after async bootstrap

I'm trying to do ssr with injected reducers using redux. However I'm seeing no store on the context when the renderToString function is executed.

I'm essentially following this repo https://github.com/dlebedynskyi/react-playground and specifically i don't see this.context.store in this component: https://github.com/dlebedynskyi/react-playground/blob/master/src/app/store/withAsyncReducers.js

That repo uses an outdated version of react async component and react async bootstrapped so I'm basically trying to do the same with the updated libraries. In the older version a variable appWithAsyncComponents would exist after the bootstrap but now that doesn't exist. Would you have any idea why the context is an empty object when rendering to string but exists before that? Any guidance would be appreciated.

//... In my middleware fn...
const app = (
    <AsyncComponentProvider asyncContext={asyncContext}>
      <Provider store={store}>
          <StaticRouter location={request.url} context={routerContext}>
              <MyApp />
          </StaticRouter>
      </Provider>
    </AsyncComponentProvider>
  )

// this.context.store exists hereπŸ‘‡
asyncBootstrapper(app).then(() => {
      // app doesn't have store here πŸ‘‡
      const appString = renderToString(app)

App hides some errors

Hi,

I'm build a route with with asyncComponent and got an unrelated error, but then component wrapped with asyncComponent, there is no any errors in browser console, just clean screen. I can catch it only via React dev tool:
cryptit_155

but, if I use component without wrapping in asyncComponent, then I got an error in browser console, like this:
cryptit_156

Exception logging using Sentry.io

Just wondering what do you recommend if I wanted to log exceptions using Sentry. The async components are catching the exception so I'm wondering is there a way to pass a handler to know when an error occurred. Or maybe are we able to get an option to not catch the exceptions?

Bake in some behaviour for module.hot reliability

I am going nuts with my AsyncComponent usage. It's awesome some of the use cases :-)

Given this I have found that the module.hot API breaks things. I need to add some behaviour that gives us specific handling for module.hot... I believe.

Infinite Loops are common and easy to occur

If ANY problem happens then I get insanely fast errors going on. We need some sort of loop detection here to handle this. Not even sure why it happens - perhaps a way to solve it? Or am I just doing something wrong?

image

IE11 and Edge has error: Parsing error: Unexpected token import

AsyncLogin.js

import { asyncComponent } from 'react-async-component';

export default asyncComponent({
  resolve: () => import('./index')
});

Use this plug-in for asynchronous loading components, other browsers can run, but in IE10 + all the browsers are reported on this error, do not know why, when the package using the babel compiler. how to solution? thanks

Typescript type definitions

I see that you already have an index.d.ts file in the project. Would be great if that was included in the npm package, or atleast made available under @types/react-async-component

Comparison with react-loadable

Hey @ctrlplusb, this library is great!

But I know when react-loadable launched, you'd considered merging the projects. Wondering if that it still on the cards? And if not, how do you compare the two libraries (pros/cons)?

Any tips in helping make that decision are much appreciated πŸ˜ƒ

Combination with "fetchData" idea from Apollo GraphQL

I was wondering how to combine this lazy loading with "fetchData()" static methods used by Apollo for prefetching data server-side during SSR. This is implemented in getDataFromTree (see: http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree)

I see you have a custom solution for loading data which might not work for us, as we need Apollo support. Currently it seems like Apollo's logic is not waiting for the chunks which actually needs the relevant data.

Maybe you can share a hint in the docs.

Component props in resolve function

Hi, there.

I have next usage question.

Before import module I need to fetch API service to resolve what module I have to import. My application uses SEO-friendly routing. For example:

  • URL /my-very-nice-article corresponds ArticlePagecomponent
  • URL /statistics-for-my-other-very-nice-article corresponds ArticleStatisticsPage
    and etc.
    All matches between URLs and components are stored in database.

So in resolve function of my async component I need to get current location from props (I use react-router v4).

All my routing consists of the next line:

<Route path="*" component={AsyncComponentResolver}/>

I didn't find any information how to use props in resolve function and write wrapper component:

class AsyncComponentResolver extends Component {
  render(){
    const {pathname} = this.props.location;
    
    const AsyncComponent = asyncComponent({
      resolve: () => {
        return API.fetchRouteInformation(pathname)
          .then(({modulePath}) => import(modulePath))
      },
      LoadingComponent: ({ match }) => <div>Resolving {match.url}</div>
    });
    return <AsyncComponent {...this.props}/>
  }
}

But this solution doesn't work in stage of server-side rendering and I have <div>Resolving /my-very-nice-article</div> in rendered HTML instead of rendered ArticlePage component.

Thanks! It will be very nice if you show me the right way.

Consider Changing Double-Mount via prop?

Might be interesting to allow components to provide a prop to override the default behavior. For example, if some components conduct actions within their componentWillMount function that we don't want the server to do and/or don't want to occur twice, we can override it by providing something like asyncComponentWillMount or something similar. If not provided, default behavior applies.

Provide a synchronous version of "withAsyncComponents"

I'm using react-async-component in a SSR context where all async components are defer=true. In this case there is no need to transfer state using STATE_IDENTIFIER from the server.

Wrapping the app in withAsyncComponents makes it harder to get working with data fetchers. We use redial and I'm quite happy with it.

I've been able to implement react-async-component synchronously by using AsyncComponentProvider directly.

import AsyncComponentProvider from 'react-async-component/commonjs/AsyncComponentProvider';
...
const app = (
  <AsyncComponentProvider execContext={createExecContext()}>
    <MyApp />
  </AsyncComponentProvider>
);

This uses internal API and I had to copy createExecContext into my project, but it works. I would like to have an official and simpler way of doing this.

dynamic require in production

Hello

My application is server side rendered.
Here is my asyncComponent which load a HomeBackground depending on a marketing code :

export default asyncComponent({
	resolve: () => new Promise(resolve => {
		require.ensure([], () => {
			const path = getStore().getState().marketing.code || "default";
			const component = require("./" + path + "/HomeBackground.jsx");
			resolve(component);
		}, "home-background-loader");
	}),
	serverMode: "resolve"
});

In dev mode, everything works perfectly.

However when I build the app for production, I encounter the following issue (marketing code here is "ul") :

capture d ecran 2017-04-28 a 11 49 43

I wonder if dynamic require is handled by react-async-component ?

What am I doing wrong ?

Best regards

React 16 Errors

Just tried an update to React 16 and it would appear an error with async component makes the app fail to load :(

warning.js:36 Warning: Failed child context type: Cannot read property 'asyncComponentsAncestor' of undefined

Says it is only a warning but my app no longer loads at all and this is the only error I can see..

Is it possible to send props to the resolved component ?

I am using react-router v4 and I have this code

import React from 'react'
import Route from 'react-router-dom/Route'

import LazilyLoad from './LazilyLoad'

const Async = route => (
  <Route
    path={route.path}
    render={props => (
      <LazilyLoad render={route.component} >
        {Component => (
          <Component {...props} routes={route.routes} />
        )}
      </LazilyLoad>
    )}
  />
)

Where LazilyLoad is my own async component. I would like to switch to react-async-component since your code is way better, but I cannot manage to send props to the resolved component.

Right now I have this code (where route.component return System.import(...))

const Async = route => (
  <Route
    path={route.path}
    component={
      asyncComponent({
        resolve: () => route.component,
      })
    }
  />
)

But I get

Warning: Failed prop type: The prop routes is marked as required in DashBoard, but its value is undefined.

since I not longer have routes={route.routes}

So, is it possible to send props to a resolved component ? If yes, how ? If no, is it desired feature that maybe I can try to implement and PR ?

Nested AsyncComponents not resolved server-side

Thanks for the awesome library! I can't tell if I'm doing something wrong or not, but I can't seem to resolve nested components. Walking the element tree after passing through withAsyncComponents server-side describes nested async components as promises as yet unresolved:

{ path: '/test/foo',
  component:
   { [Function: AsyncComponent]
     childContextTypes: { asyncComponentsAncestor: [Object] },
     contextTypes: { asyncComponents: [Function: bound checkType] },
     displayName: 'AsyncComponent' },
  },
}

The tree could be described as thus:

// in routes.js
const baseTestRoute = (
  <Route
    path={'/test'}
    component={createAsyncComponent({
      resolve: () => import('./routes/NestedTestRoute'),
    })
    }
  />
);

// in ./routes/NestedTestRoute.js
export default function NestedTestRoute({ match }) {
  return (
    <Switch>
      <Route path={match.url} exact component={() => <h1>renders just fine, hooray!</h1>} />
      <Route
        path={`${match.url}/foo`}
        component={createAsyncComponent({
          resolve: () => import('./DeeplyNestedRoute'),
          Loading: () => <h1>this displays on the server</h1>,
        })}
      />
    </Switch>
  );
}

// in DeeplyNestedRoute.js
export default function DeeplyNestedRoute({ match }) {
  return (
      <Route path={`${match.url}/bar`} component={SomeComponentThatDoesNotRender} />
  );
}

If you need me to stand up a small application demonstrating the problem, I can definitely do as such.

SSR + SEO: render on server but defer client loading

I'm trying to optimize my loading strategy. My number one concern is SEO. I want server side rendering, but I also want the end user to have a smart incremental loading experience.

I was wondering if it would be possible to have a mode that renders on the server, but defers client side loading. This way, you could have something on screen, and available to the Google bot, that wasn't interactive yet. A LoadingComponent in this scenario could be designed as a transparent overlay, to be hidden once client side loading completes.

How to write tests?

I have a container that loads a component async with react-async-component and then perform the connection with redux's state.

It works nicely, however, I could not find a way to test it. The component (obviously) is not ready when the tests runs. In this case enzyme finds an empty react component.

Code examples:

ForgotPage.js

// @flow
import { createAsyncComponent } from 'react-async-component';
import { connect } from 'react-redux';
import requestPasswordRecovery from '../../shared/state/actions/passwordRecovery';

const Forgot = createAsyncComponent({
  resolve: () => new Promise(resolve =>
    // $FlowFixMe
    require.ensure([], (require) => {
      resolve(require('../components/Forgot'));
    }, 'forgot'),
  ),
  ssrMode: 'boundary',
});

const mapStateToProps = state => ({
  passwordRecovery: state.passwordRecovery,
});

const mapDispatchToProps = dispatch => ({
  requestPasswordRecovery: data => dispatch(requestPasswordRecovery(data)),
});

export default connect(mapStateToProps, mapDispatchToProps)(Forgot);

This code does not work with my integration tests. If, instead of loading it async I just import it, tests run fine.

Is there a way to get this working with tests?

window object + naming bundles

Hi @ctrlplusb ! Nice library!

I am implementing it and it all works perfectly. Although I think I am not setting it up properly.

client

const renderApp = app => {
    let shell = <AppContainer>
        <WithStylesContext onInsertCss={styles => styles._insertCss()}>
            <MuiThemeProvider muiTheme={theme}>
                <Provider store={store}>
                    <BrowserRouter router={app}/>
                </Provider>
            </MuiThemeProvider>
        </WithStylesContext>
    </AppContainer>;
    withAsyncComponents(shell).then(({appWithAsyncComponents}) =>
        render(appWithAsyncComponents, rootEl),
    );
};


renderApp(SpotifyApp);


if (NODE_ENV === 'development' && module.hot) {
    module.hot.accept("./app.tsx", () => {
        const NextApp = require("./app.tsx").default;
        renderApp(NextApp);
    });
}

app

// also wondering how to name the splitted chunks?

const AsyncDashboard = createAsyncComponent({
    resolve: () => new Promise(resolve =>
        require.ensure([], (require) => {
            resolve(require("./components/dashboard/dashboard"));
        }, "dashboard.js")),
});


export let SpotifyApp = () => {
    return (
        <div>
            <Match exactly pattern="/" component={Shell(AsyncProduct)}/>
            <Match exactly pattern="/dashboard" component={Shell(AsyncDashboard)}/>
            <Miss component={NoMatch}/>
        </div>
    )

};

server

export default  () => (request, response) => {
    const context = createServerRenderContext();
    const result = context.getResult();
    if (result.redirect) {
        response.redirect(302, `${result.redirect.pathname}${result.redirect.search}`);
    } else {

        if (result.missed) {
            response.status(404);
        } else {
            response.status(200);
        }
        let css = []; // CSS for all rendered React components

        let App =
            <WithStylesContext onInsertCss={styles => css.push(styles._getCss())}>
                <MuiThemeProvider muiTheme={getMuiTheme({userAgent: request.headers['user-agent']})}>
                    <Provider store={createStore(allReducers,allReducersInitial)}>
                        <ServerRouter location={request.url} context={context}>
                            <SpotifyApp/>
                        </ServerRouter>
                    </Provider>
                </MuiThemeProvider>
            </WithStylesContext>;
        withAsyncComponents(App)
            .then((result) => {
                const {
                    appWithAsyncComponents,
                    state,
                    STATE_IDENTIFIER
                } = result;
                const markup = ReactDOMServer.renderToString(appWithAsyncComponents);
                let SerialState = require('serialize-javascript')(state);
                console.log(SerialState);
                response.send("<!DOCTYPE html>" +
                    ReactDOMServer.renderToStaticMarkup(
                        <UniversalShell css={css}
                                        state={SerialState}
                                        STATE_IDENTIFIER={STATE_IDENTIFIER}
                                        userAgent={request.headers['user-agent']}
                                        content={markup}/>
                    ));
            });
    }
};

I am using renderToStaticMarkup so I was not able to set the window object.

<script type="text/javascript"
                dangerouslySetInnerHTML={
                {__html:
                    "window.__REACT_ASYNC_COMPONENTS_STATE__ = "+ this.props.state+";"+
                "var HEY = "+ this.props.state+";"
                }
            }
        />

On the console I can get HEY but cannot get window.REACT_ASYNC_COMPONENTS_STATE
Weird thing is that the state is always {"resolved":{}} i was expecting this information to be really important, but it seems it is always empty even though it works flawlessly.

Weird error when creating asyncComponent with router

Warning: Failed child context type: Cannot read property 'asyncComponentsAncestor' of undefined
    in AsyncComponent (created by Route)
    in Route
    in div
    in Router (created by ConnectedRouter)
    in ConnectedRouter
    in Provider

looks like there is no validation - that context could not exist - so it throws that error - but after that works as expected (from my point of view)

Add support for loading multiple imports per component (localization)

I'd like to see support for loading locale-specific i18n message files together with the actual view component. This would allow for route-splitted translation files which, as the view itself, are just loaded on demand e.g. for usage with IntlProvider by react-intl.

Conceptually it might look like:

const AboutView = createLazyComponent({
  load: (language) => {
    return [
      import("./views/About"),
      import("./views/About." + language + ".json")
    ]
  }
})

but int theory instead of an array we could also use an object/dict for being more specific:

const AboutView = createLazyComponent({
  load: (language) => {
    return {
      view: import("./views/About"),
      messages: import("./views/About." + language + ".json")
    }
  }
})

If there are messages it would probably be a good idea to use IntlProvider with the messages given for wrapping the dynamically loaded view. It might be also cool to use injectIntl for offering the intl API via props to the loaded view.

What do you think?

Getting weird flashing when navigating

I've found that when I use this to async load a component, if I navigate to a different route that uses the same component, the app briefly hits the loading component, before re-mounting the resolved component. No network request is made, since it's already been loaded, but I do get a brief flash of the loading component. Is there any way to prevent this, or is this known/expected behavior?

Just to let you know it's pure magic!

Hi :)

Sorry for the not so useful issue here but thanks a bunch for the lib!
I was starting to have a similar API myself but couldn't manage my way out.

If I can be of any help here, feel free to reach out. I'll be glad to give a hand!

Cannot find module (on the server side)

Works on client, but cannot find module on server side:

.babelrc

{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions"]
      }
    }],
    "es2015",
    "react",
    "stage-3"
  ],
  "plugins": [
    "react-hot-loader/babel"
  ]
}

webpack.config

[{
  name: 'client',
  target: 'web',
  entry: {
    bundle: [
      'react-hot-loader/patch',
      'webpack-hot-middleware/client',
      'webpack/hot/only-dev-server',
      path.join(srcDir, 'index.js')
    ],
    vendor: vendor
  },
  output: {
    path: webpackProdConfig.output.path,
    chunkFilename: '[name].js',
    filename: '[name].js',
    publicPath: '/'
  }
  ...
},
{
  name: 'server',
  target: 'node',
  entry: path.resolve(__dirname, '../server/serverRenderer.js'),
  output: {
    path: webpackProdConfig.output.path,
    filename: '[name].js',
    chunkFilename: '[name].js',
    libraryTarget: 'commonjs2'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader', 'eslint-loader'],
        exclude: /node_modules/
      }
    ]
  }
  ...
}]

AsyncAbout/index.js

import { asyncComponent } from 'react-async-component';

export default asyncComponent({
  resolve: () => new Promise(resolve =>
    require.ensure([], (require) => {
      resolve(require('./About'));
    }, 'about')
  )
});

AsyncAbout/About.js

import React from 'react';

export default () => <h1>About</h1>;

Compilation output:

Child client:
    chunk    {0} post-page.js, post-page.js.map (post-page) 5.57 kB {2} [rendered]
    chunk    {1} about.js, about.js.map (about) 610 bytes {2} [rendered]
    chunk    {2} bundle.js, bundle.js.map (bundle) 305 kB {3} [initial] [rendered]
    chunk    {3} vendor.js, vendor.js.map (vendor) 1.22 MB [entry] [rendered]
Child server:
    chunk    {0} post-page.js (post-page) 5.57 kB {2} [rendered]
    chunk    {1} about.js (about) 610 bytes {2} [rendered]
    chunk    {2} main.js (main) 1.21 MB [entry] [rendered]
webpack: Compiled successfully.

Files are served with dev-server so they are not written to disk.

After navigating to http://localhost:3000/about, got this error on the server side:

Failed to resolve asyncComponent
{ Error: Cannot find module './about.js'
    at Function.Module._resolveFilename (module.js:469:15)
    at Function.Module._load (module.js:417:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Function.requireEnsure [as e] (/var/www/public/main.js:41:25)
    at /var/www/public/main.js:18928:34
    at resolve (/var/www/public/main.js:18927:12)
    at getResolver (/var/www/public/main.js:25629:24)
    at AsyncComponent.resolveModule (/var/www/public/main.js:25719:16)
    at doResolve (/var/www/public/main.js:25671:25) code: 'MODULE_NOT_FOUND' }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.