Parcel: Workaround for React.lazy occasionally not working in Parcel

Created on 24 Oct 2018  路  12Comments  路  Source: parcel-bundler/parcel

Not really an issue at this point, but I wanted to help out the lost souls who would like to test new features of React 16.6 so early.

If you coded something similar to the code from React.lazy docs:

import React, {lazy, Suspense} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <OtherComponent />
    </Suspense>
  );
}

you may get the following error:

Warning: lazy: Expected the result of a dynamic import() call. Instead received: function OtherComponent() {}

Your code should look like: const MyComponent = lazy(() => import('./MyComponent'))

But hold on! Your code already looks like const MyComponent = lazy(() => import('./MyComponent'))! This means that the component you're using is not using Harmony modules, but likely CommonJS modules, which work a little differently, and React got confused.

Fear not! A quick solution is to wrap native import, which gets transformed by Parcel, in your own Promise which would return Harmony module-like result, like so:

const OtherComponent = lazy(() => new Promise((resolve, reject) => {
  import('./OtherComponent')
    .then(result => resolve(result.default ? result : { default: result }))
    .catch(reject);
}));

Happy coding!

Question

Most helpful comment

Hi Guy's,

const OtherComponent = lazy(() => import('./OtherComponent')); => will only work for default component exports not for the named export.

All 12 comments

Not sure why this is necessary, I've experimented with the lazy both using function components and class components, and they both work without any need for this hack.

Would be cool to know how OtherComponent looks in your tests, to see if I could potentially help out in getting rid of this hack

In my specific case, I've been using react-markdown. It may be using non-Hamony modules under the hood. Some less experienced developers may have no idea on how to work around that issue.

Here's the app I used this workaround in, obviously because I had to: https://github.com/wojtekmaj/webpack-summary-compare

That saved me a lot of time indeed. Thank you 馃挴

anyone who have other solutions? it seems strange.

@allan2coder It is a little strange, but I'll try my best to explain why.

If you have a CommonJS module:

module.exports = class Foo extends Component {}

then, when import()ed, will result in result being Component

If you have a Harmony/ES6 module:

export default class Foo extends Component {}

then, when import()ed, will result in result being { default: Component }.

So, I wrap import() in a promise, which resolves with:

resolve(result.default ? result : { default: result }))

So if there's result.default, we know we deal with ES6 module and we can safely return result as given by import(). If not, we wrap the result in an object. This ensures that our promise will always return a ES6-like output, being { default: Component }. Why is that even necessary? This is because React.lazy implementation expects a ES6 module - it's not made for CommonJS modules at all.

Of course, if you have control over the module system used, you can switch to ES6 modules and get all the benefits of it. But sometimes you may want to lazily import the modules you don't have the control over, and that's when this workaround is necessary.

Hope this helps!

This seems like an issue that React should solve rather than Parcel. They seem to be requiring the module you are importing to be an ES6 module, and looking for the default property on it. Seems like they could do this interop within React itself.

It's absolutely a React thing, but I don't think they are willing to change it - see facebook/react#13886. I'm just trying to help the community :)

Does const MyComponent = lazy(() => import('./MyComponent')) work correctly with Webpack?

No @mischnic, it would be affected by the same issue.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs.

Hi Guy's,

const OtherComponent = lazy(() => import('./OtherComponent')); => will only work for default component exports not for the named export.

https://reactjs.org/docs/code-splitting.html#reactlazy:

that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oliger picture oliger  路  3Comments

algebraic-brain picture algebraic-brain  路  3Comments

Niggler picture Niggler  路  3Comments

dotdash picture dotdash  路  3Comments

davidnagli picture davidnagli  路  3Comments