React: Allow for named imports in React.lazy

Created on 16 Jan 2019  路  7Comments  路  Source: facebook/react

Do you want to request a feature or report a bug?

Feature

What is the current behavior?

Currently, React.lazy only accepts an anonymous function that returns a dynamic import object. readLazyComponentType then pulls the default export off of that import object. This does not allow for importing named imports from a module.

The official workaround, according to the React docs:

If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default.

Another, more concise workaround:

const Contacts = React.lazy(() =>
  import('./Contacts.js').then(module => ({ default: module.Contacts }))
);

What is the proposed behavior?

I'd be willing to submit a PR that optionally accepts named imports but defaults to importing the default import. It would look like:

thenable.then(
-  moduleObject => {
+  resolvedComponent => {
    if (lazyComponent._status === Pending) {
-      const defaultExport = moduleObject.default;
+      if (resolvedComponent.default) {
+        resolvedComponent = resolvedComponent.default
+      }
      if (__DEV__) {
-        if (defaultExport === undefined) {
+        if (resolvedComponent === undefined) {
          warning(
            false,
-            'lazy: Expected the result of a dynamic import() call. ' +
+            'lazy: Expected a promise that resolves to a React component. ' +
              'Instead received: %s\n\nYour code should look like: \n  ' +
              "const MyComponent = lazy(() => import('./MyComponent'))",
-            moduleObject,
+            resolvedComponent,
          );
        }
      }
      lazyComponent._status = Resolved;
-      lazyComponent._result = defaultExport;
+      lazyComponent._result = resolvedComponent;
    }
  },
  error => {
    if (lazyComponent._status === Pending) {
      lazyComponent._status = Rejected;
      lazyComponent._result = error;
    }
  },
);

This will also require some updates to the docs.

Let me know your thoughts before I start...

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

[email protected], Chrome 71, macOS 10.14

cc @acdlite @gaearon

Most helpful comment

Working with default exports on Function Components is very inconvenient. I hope the React team can revisit this soon. IMO React.lazy is a very powerful tool, so I created a small abstraction of @gnestor's workaround to make the lazy import a bit simpler. When used with TypeScript it's 100% type-safe and keeps the look and feel of named imports. I hope this helps! 馃檪

function lazyImport<
  T extends React.ComponentType<any>,
  I extends { [K2 in K]: T },
  K extends keyof I,
>(
  factory: () => Promise<I>,
  name: K
): I {
  return Object.create({
    [name]: React.lazy(() => factory().then(module => ({ default:  module[name] })))
  });
}

// Usage
const { Home } = lazyImport(() => import("./Home.component.tsx"), "Home");

PS: The Object.create(..) is to avoid conflicts with the object key and the K type. Another way would be to cast the object as unknown as I, but I think this looks a bit cleaner that way 馃檪

All 7 comments

Thanks but it's an intentional design decision to not currently allow named imports. We may reconsider it later. You can find the relevant discussion in https://github.com/reactjs/rfcs/pull/64.

This may seem stupid but I expected the feature to have the following syntax:

React.lazy('./MyComponent');

No more extra function or import required

This may seem stupid but I expected the feature to have the following syntax:

React.lazy('./MyComponent');

No more extra function or import required

It's not working, but I have tried this ->

React.lazy(() => import('../MyComponent'), 'default');

It works fine!

This makes SFC components pretty annoying to use...

Summary of why named exports are not baked in and a workaround can be found in https://github.com/reactjs/rfcs/pull/64#issuecomment-431507924

npm audit said I needed to update to react-scripts 4.0.0 as the only solution to a vulnerability. After upgrade, new ESLint rules went into effect warning about default exports. So, I removed all my default exports and replaced them with named. Now, my router doesn't work.

Thanks so much for not supporting this and making things harder on me. Probably time to reconsider this issue and the comment linked to in the previous comment.

Working with default exports on Function Components is very inconvenient. I hope the React team can revisit this soon. IMO React.lazy is a very powerful tool, so I created a small abstraction of @gnestor's workaround to make the lazy import a bit simpler. When used with TypeScript it's 100% type-safe and keeps the look and feel of named imports. I hope this helps! 馃檪

function lazyImport<
  T extends React.ComponentType<any>,
  I extends { [K2 in K]: T },
  K extends keyof I,
>(
  factory: () => Promise<I>,
  name: K
): I {
  return Object.create({
    [name]: React.lazy(() => factory().then(module => ({ default:  module[name] })))
  });
}

// Usage
const { Home } = lazyImport(() => import("./Home.component.tsx"), "Home");

PS: The Object.create(..) is to avoid conflicts with the object key and the K type. Another way would be to cast the object as unknown as I, but I think this looks a bit cleaner that way 馃檪

Was this page helpful?
0 / 5 - 0 ratings