React - import component dynamically
In this short article, we would like to show how to import components dynamically in React.
Quick solution:
import React from 'react';
const MyComponent = React.lazy(() => import('./Mycomponent'));
Custom component import logic
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:
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:
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(); |
Universal component import logic
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:
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:
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.