React - import component dynamically
In this short article, we would like to show how to import components dynamically in React.
Quick solution:
xxxxxxxxxx
import React from 'react';
const MyComponent = React.lazy(() => import('./Mycomponent'));
This section shows, how to create custom logic that loads indicated component when the first time is used.
That kind of component attaching can be useful when:
- we don't want to load some parts until it are used,
- component logic is located on a different server,
- when we use Server-Side Rendering (SSR), the attached component uses BOM (Browser Object Model, like:
window
,location
,navigator
, etc.) and we want to avoid internal problems withundefined
BOM objects in compilation with NodeJS (SSR in Gatsby/Preact has problems with JS libraries that callswindow
,location
,nagator
, etc.).
Note:
The below example shows how to createuseComponent
hook that let us to load component withasync
import - be sure that building tool is configured to compile separated bundle for that imports.
useComponent.jsx
file:
xxxxxxxxxx
import { useState } from 'react';
let componentPromise = null;
let componentModule = null;
// The method runs component importing.
//
const importComponent = () => {
if (componentModule) {
return Promise.resolve(componentModule);
}
if (componentPromise) {
return componentPromise;
}
componentPromise = import('/path/to/my/component') // change it to something
.then((module) => {
componentModule = module;
return module;
});
return componentPromise;
};
// Uncomment the below line to start module loading as soon as it possible.
//
// prepareComponent();
const useComponent = () => {
const [component, setComponent] = useState(componentModule);
if (component === undefined) {
importComponent()
.then(setComponent) // we just wait until component is ready
.catch(console.error); // we want to see loading exceptions in console
}
return component;
};
export default useComponent;
Usage example:
xxxxxxxxxx
import React from 'react';
import useComponent from './useComponent';
const App = () => {
const Component = useComponent();
return (
<div>
{Component ? <Component.default /> : <span>Component is not imported yet!</span>}
</div>
);
};
export default App;
Note: change
Component.default
to proper export name if it is necessary.
Example import names:
static import | dynamic import |
import MyComponent from './my-component'; | const Component = useComponent(); const MyComponent = Component.default; |
import {MyComponent1, MyComponent2} from './my-component'; |
const Component = useComponent(); |
In this section, we would like to show how to modify the above logic to import different components with the same logic.
useComponent.jsx
file:
xxxxxxxxxx
import { useState } from 'react';
const statuses = { }; // keeps information about imported modules
// The method runs component importing.
//
export const importComponent = (id, execute) => {
const status = statuses[id] ?? (statuses[id] = {});
if (status.module) {
return Promise.resolve(status.module);
}
if (status.promise) {
return status.promise;
}
status.promise = execute()
.then((module) => {
status.module = module;
return module;
});
return status.promise;
};
const useComponent = (id, execute) => {
const [component, setComponent] = useState(() => {
const status = statuses[id];
return status ? status.module : undefined;
});
if (component === undefined) {
importComponent(execute)
.then(setComponent) // we just wait until component is ready
.catch(console.error); // we want to see loading exceptions in console
}
return component;
};
export default useComponent;
Usage example:
xxxxxxxxxx
import React from 'react';
import useComponent, {importComponent} from './useComponent';
// Uncomment below line to start module loading as soon as it possible.
//
// importComponent('MyComponent', () => import('/path/to/my/component'));
const App = () => {
const Component = useComponent('MyComponent', () => import('/path/to/my/component')); // change it to something
return (
<div>
{Component ? <Component.default /> : <span>Component is not imported yet!</span>}
</div>
);
};
export default App;
Note:
Some building tools resolves
import('/path/to/component/module')
paths that makes impossible to use variables with imports, e.g.import(componentPath)
- Webpack has that. This is reasone why we should callimport()
with string literal.