React - import component dynamically
In this short article, we would like to show how to import components dynamically in React.
That kind of component attaching can be useful when:
- we don't want to load all web application logic until some part of them is 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 has problems with JS libraries that callswindow
,location
,nagator
, etc.).
Note: below example shows how to create
useComponent
hook that let us to load component withasync
import.
Quick solution (useComponent.jsx
file):
import { useState } from 'react';
let componentPromise = null;
let componentModule = null;
// the method runs component import only once
// with waiting until operation is completed for other calls
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 below line to start module loading as soon as it possible
// loaded module before do not cause re-rendering / state changes later
//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 above logic to import different components indicating component paths.
import { useState } from 'react';
const statuses = { }; // keeps information about imported modules
// the method runs component import only once
// with waiting until operation is completed for other calls
const importComponent = (path) => {
const status = statuses[path];
if (status.module) {
return Promise.resolve(status.module);
}
if (status.promise) {
return status.promise;
}
status.promise = import(path)
.then((module) => {
status.module = module;
return module;
}));
return status.promise;
};
// uncomment below line to start module loading as soon as it possible
// loaded module before do not cause re-rendering / state changes later
//prepareComponent();
const useComponent = (path) => {
const [component, setComponent] = useState(() => {
const status = statuses[path];
return status ? status.module : undefined;
});
if (component === undefined) {
importComponent(path)
.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('/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 transpilers resolves
import('/literal/as/path/to/component/module')
paths during transpilation that makes impossible to use variables with import, e.g.import(pathToMyComponentModule)
- Gatsby has that. The solution for the problem is to edit the above code and replace the path with a callback that returns the import result. Finally, we should use:const Component = useComponent(() => import('/path/to/my/component'));