Can you deconstruct lazily loaded React components?
Asked Answered
K

7

29

Using es6 imports, you can do this:

import { MyComponent } from "../path/to/components.js";

export default function () {
  return <MyComponent/>;
}

Can I do it with React.lazy too?

const { MyComponent } = lazy(() => import("../path/to/components.js"));

I get the following error, but I'm not sure if it's related to this or some other bug I have:

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

Klemens answered 10/11, 2019 at 18:20 Comment(0)
B
22

You can if you use react-lazily.

import { lazily } from 'react-lazily';
const { MyComponent } = lazily(() => import("../path/to/components.js"));

It also allows importing more than one component:

const { MyComponent, MyOtherComponent, SomeOtherComponent } = lazily(
  () => import("../path/to/components.js")
);

See this answer for more options.

Burchell answered 8/1, 2021 at 2:53 Comment(3)
What a beautiful library, thank you so much for this. Can I apply lazily to 3rd party npm packages directly ?Wace
Can I apply lazily to 3rd party npm packages directly ?Wace
@Wace probably, I don't know if that will provide the best bundle size though, so make sure to test if adding a separate file to re-export that component from package will tree-shake it betterBurchell
C
41

Here is how I did it when I faced this problem with FontAwesome:

const FontAwesomeIcon = React.lazy(()=> import('@fortawesome/react-fontawesome').then(module=>({default:module.FontAwesomeIcon})))
Convivial answered 20/2, 2021 at 7:34 Comment(0)
B
22

You can if you use react-lazily.

import { lazily } from 'react-lazily';
const { MyComponent } = lazily(() => import("../path/to/components.js"));

It also allows importing more than one component:

const { MyComponent, MyOtherComponent, SomeOtherComponent } = lazily(
  () => import("../path/to/components.js")
);

See this answer for more options.

Burchell answered 8/1, 2021 at 2:53 Comment(3)
What a beautiful library, thank you so much for this. Can I apply lazily to 3rd party npm packages directly ?Wace
Can I apply lazily to 3rd party npm packages directly ?Wace
@Wace probably, I don't know if that will provide the best bundle size though, so make sure to test if adding a separate file to re-export that component from package will tree-shake it betterBurchell
D
6

Of course you can. It's an honest mistake many has made,

const sub = 'a'
const obj = { a: 'alpha', b: 'beta' }

obj.sub // wrong (accessing a direct key)
obj[sub] // right (computed property)

the same mistake slipped through for many. This is a work in progress but worked like a charm, and thanks for all the other answers to tailor it to my need.

const ComponentFactory = ({ componentName, ...props }) => {
  const Component = lazy(() => import('baseui/typography').then((module) => ({ default: module[componentName] })))

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Component {...props} />
    </Suspense>
  )
}

usage:

    <ComponentFactory
      componentName='Paragraph1'
      margin='0.1rem 0rem 0.25rem 0.3rem'
      color={style[of].headingText}
    >
      {headingMessage}
    </ComponentFactory>
Debutant answered 21/3, 2022 at 17:12 Comment(0)
R
4

React.lazy currently only supports default exports. If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default. This ensures that tree shaking keeps working and that you don’t pull in unused components.

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));

More info: https://reactjs.org/docs/code-splitting.html#named-exports

Ruelle answered 10/11, 2020 at 13:21 Comment(0)
R
2

You can't with React.lazy :

React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component. (cf. https://reactjs.org/docs/code-splitting.html#reactlazy)

A workaround for that exists: creating an intermediate module that imports your named export and exports it as default (cf. https://reactjs.org/docs/code-splitting.html#named-exports)

Rahmann answered 10/11, 2019 at 18:26 Comment(0)
T
1

I'd like to another workaround. This compotent chains the promise and adds the named export to the default export. src. Although, I'm not sure if this breaks tree shaking. There's a bit of an explanation here.

import {lazy} from 'react'
export default (resolver, name = 'default') => {
  return lazy(async () => {
    const resolved = await resolver()
    return {default: resolved[name]}
  })
}

Tysontyumen answered 18/5, 2020 at 22:6 Comment(0)
D
0

You can resolve a promise along with the lazy loading and this way resolve your named export.

The syntax is a bit funky, but it is working:

const MyComponent = React.lazy(
  () =>
    new Promise(async (resolve) => {
      const module = await import('../path/to/components.js');
      resolve({ ...module, default: module.default });
    }),
);

Distrustful answered 25/11, 2022 at 10:13 Comment(2)
not work, error Argument of type 'typeof import("/media/dimaslanjaka/DATA/Repositories/page/node_modules/rsuite/esm/Nav/index")' is not assignable to parameter of type '{ default: never; } | PromiseLike<{ default: never; }>'.Statistician
Not an expert here, but maybe this is related to this being an ESM module? I am guessing a bit here - but maybe this thread could be useful? #54978243Distrustful

© 2022 - 2024 — McMap. All rights reserved.