I have 2 components, I want to load all of them using lazy load something like
const A = lazy(() => import("../test/A"));
const B = lazy(() => import("../test/B"));
This will create 2 separate bundles, and will import them when required.
But, I want to create a single bundle and when that bundle loads I should be able to use both above components.
I also don't want to create a single component containing both the above components as I want a separate route for both of them
I tried to do something like this =/src/App.js
Can please somebody will explain me Is this type of functionality possible, If yes then how and what am I doing wrong
I have 2 components, I want to load all of them using lazy load something like
const A = lazy(() => import("../test/A"));
const B = lazy(() => import("../test/B"));
This will create 2 separate bundles, and will import them when required.
But, I want to create a single bundle and when that bundle loads I should be able to use both above components.
I also don't want to create a single component containing both the above components as I want a separate route for both of them
I tried to do something like this https://codesandbox.io/s/eager-raman-mdqzc?file=/src/App.js
Can please somebody will explain me Is this type of functionality possible, If yes then how and what am I doing wrong
Share Improve this question asked Apr 27, 2020 at 17:06 AdarshAdarsh 5242 gold badges10 silver badges19 bronze badges 5 |5 Answers
Reset to default 10There are probably some tuning options in the code splitter that might better accomplish what you are trying to achieve. But if you don't want to mess around with those (or they are not available to change because you are using a preset configuration), then perhaps you can combine the modules into a single file, and lazy load that "combo module".
To do that, we first need to know how lazy
determines what component in a module to load, and what types of objects it expects. From the docs:
The
React.lazy
function lets you render a dynamic import as a regular component.
React.lazy
takes a function that must call a dynamicimport()
. This must return aPromise
which resolves to a module with a default export containing a React component.The lazy component should then be rendered inside a
Suspense
component, which allows us to show some fallback content (such as a loading indicator) while we’re waiting for the lazy component to load.You can place the
Suspense
component anywhere above the lazy component. You can even wrap multiple lazy components with a singleSuspense
component.
So according to that, if you want to use lazy()
to wrap the module, then you have to have a component as the default
property of the module. So it won't allow you to automatically use a module that uses named exports as a component. However, you can easily make a promise that transforms a named export to a default export, and wrap that in lazy:
// in comboModule.js:
export A from '../test/A'
export B from '../test/B'
// in the code that needs lazy modules
const A = lazy(() => import('./comboModule').then((module) => ({default: module.A})))
const B = lazy(() => import('./comboModule').then((module) => ({default: module.B})))
Note that we have to call import
inside the initializer function passed to lazy
, or the import will start immediately. Part of lazy
's benefit is that is lets you wait until the parent component renders the lazy component before loading. However, import()
should cache the result from the first import, and only load the code once.
In the initializer function, we use then
to transform the result of import()
from something like Promise({A: <Component>, B: <Component>})
to what lazy expects from the initializer function: Promise({default: <Component>})
Now we have two lazy components that both source from one module file.
Resources:
- React code splitting
- import/export
Promise.prototype.then
(then
returns a promise)
You can use Suspense for waiting both of them. There are two bundles, but you can wait of loading both of them
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
I wrote a utility function that generalizes what @garrett-motzner said in his answer:
import {lazy} from 'react';
export const multiLazy = moduleLoaderArray => {
const promises = Promise.all(moduleLoaderArray.map(loader => loader()));
return moduleLoaderArray.map((m, index) =>
lazy(() => promises.then(results => results[index]))
);
}
Works like a charm like this:
const [A, B] = multiLazy([
() => import("../test/A"),
() => import("../test/B"),
]);
So no need to create an intermediary component file (besides for the utility function of course)
I chose this syntax to stick as close as possible to the React.lazy
one, but it could be modified to get multiLazy(['../test/A', '../test/B'])
if preferred and often used.
You can use a proxy wrapper to the import
statement to give React.lazy
what it wants:
const bundle = new Proxy(import("../CombineModule"),{
"get":function (esm,key){
return esm.then(function (m){
return {"__esMODULE":true,"default":m.default[key]};
});
}
});
var CompA = React.lazy(() => bundle.A)
var CompB = React.lazy(() => bundle.B)
Based on Garrett Motzner answer I made this helper for loading named components:
// in comboModule.js:
export default from '../test/A'
export B from '../test/B'
// in the code that needs lazy modules
const m = () => import('comboModule')
const ADefault = lazy(m);
const BNamed = lazy(m, m => m.B);
// the new lazy helper function:
export function lazy<T extends React.ComponentType<any>, Q extends { default: T }>(
factory: () => Promise<Q>,
selector?: (arg: Q) => () => JSX.Element
) {
if (!selector) return React.lazy(factory);
return React.lazy(() =>
factory().then((module) => ({ default: selector(module) }))
);
}
It does need a default export to exists or otherwise TypeScript will complain about that, but you can also load named components from the module.
Promise.all()
? Wouldn't that do what you want? I'm not sure I understand exactly what you are trying to do. – zero298 Commented Apr 27, 2020 at 17:15