Reputation: 323
I need to load a react component dynamically.
I get name of the component to load as a string from user. I'm using webpack.
How do I load the component dynamically instead of having a static import statement. It seems Require.Ensure
doesn't evaluate expressions . What I want to achieve is something like this.
require.ensure([ "./widgets/" + componentName ] ,(require) => {
let Component = require("./widgets/" + componentName);
});
But this doesn't seem to work.
Upvotes: 32
Views: 50686
Reputation: 1142
I was searching for a solution, then i found out react-router npm package.
https://www.npmjs.com/package/react-loadable
Here is my example :
Loading component with name dynamically. First get loadables :
const MyComponent = Loadable({
loader: () => import(`../charts/${some_name}`),
loading: () => <div>Loading...</div>,
});
Then you have it, you can render it :
<MyComponent />
This reading may help also :
https://itnext.io/react-loading-components-dynamically-a9d8549844c4
Upvotes: 0
Reputation: 61
There are a lot of good answers here. Another approach I found is to lazy load the component as a micro app. The host application does not even need to install or import the component at design time.
import React from 'react';
import ReactDOM from 'react-dom';
import MicroApp from '@schalltech/honeycomb-react-microapp';
const App = () => {
return (
<MicroApp
config={{
View: {
Name: 'redbox-demo',
Scope: 'beekeeper',
Version: 'latest'
}
}}
/>
);
});
export default App;
I experimented with this by following along with the link below. Its a pretty interesting concept.
https://github.com/Schalltech/honeycomb-marketplace
Upvotes: 0
Reputation: 1453
As of React 16.6.0 (October 23, 2018), React.lazy()
may be a convenient solution, under the banner of code-splitting for client-side rendering.
Given the following component you wish to lazy load (MyLazy.js
):
import React from 'react';
export default function (props) {
return (
<p>My component</p>
);
}
You could render this from another component (say App.js
) with the following, noting that the built-in <Suspense>
component will display until loading is complete:
import React, { Suspense } from 'react';
export function App(props) {
const MyLazyLoaded = React.lazy(() => import('./MyLazy'));
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<MyLazyLoaded />
</Suspense>
</div>
);
}
If you want to dynamically set the name of the component, e.g. from user input per your question, you could make the import statement variable, e.g.:
var lazyComponentName = 'MyLazy';
const MyLazyLoaded = React.lazy(() => import('./' + lazyComponentName));
Note that if you do not concatenate and use the variable name directly(i.e. import(lazyComponentName)
), you will be presented with the following ES Lint warning, related to webpack:
Critical dependency: the request of a dependency is an expression
Upvotes: 4
Reputation: 10936
import React, { Component } from "react";
class DynamicLoader extends Component {
render() {
const COMPONENTS = {
CompA: require('./CompA'),
CompB: require('./CompB'),
}
if(this.props.component && COMPONENTS[this.props.component]) {
let Component = COMPONENTS[this.props.component].default;
return <Component />
}
}
}
export default DynamicLoader;
<DynamicLoader component="CompA" />
Upvotes: 1
Reputation: 613
I had a different solution due to my issue being a little different, I looked for an answer for a while but couldn't find one. Hopefully this may help someone.
My issue was that we had several applications deployed independently as different modules within a core UI. These had to be completely independently deployable within having any effect on the others.
My solution was to have the sub component applications wrapped as web pack libraries by setting these webpack properties:
libraryTarget: 'umd',
globalObject: 'this',
library: 'SubComponentName'
I think provided 2 entry points for the application based on it being accessed either directly or through a parent component within a separate application
class Library extends Component {
render() {
return (
< Provider store = {store} >< BrowserRouter>
< App isAccessDirect={this.props && this.props.isAccessDirect || false} roles={this.props.roles}>
</App>
< /BrowserRouter>< /Provider>
)
}
}
class Launcher extends Component {
render() {
return (
ReactDOM.render(
<Library isAccessDirect="true"/>,
document.getElementById('root')
)
)
}
}
Once this app is built I can access it directly in the index.html with
<script>
new Compression.Launcher().render();
</script>
Or if you wish to access the application via a separate host / component as was my requirement you can do so by using a combination of loadjs and react.
loadjs(['../sub/component/bundle.min.js'], 'loadSubComponent');
var subComponent = this.getUI('subComponentWrapperElement').get(0);
loadjs.ready('loadSubComponent', function() {
var roles = my.app.roles;
ReactDOM.render(
<SubComponentName.Library roles={roles}></SubComponentName.Library>,
subComponent
);
});
This can load in a react component in any type of javascript client side framework, in our case we used backbone within the wrapper application. The bundle.min.js was also located behind a reverse proxy but it could be located anywhere, its just essential that you load it in and await the load to finish, which loadjs was perfect for in this case. One final important bit of information was that destruction of the component was vital for ensuring a good user experience, as without we encountered a number of issues.
loadjs.reset();
var subComponent = this.getUI('subComponentWrapperElement').get(0);
ReactDOM.unmountComponentAtNode(subComponent);
So in a nut shell what I feel this provides over the other answers is a completely independently deployable suite of applications that can be completely framework agnostic.
Upvotes: 1
Reputation: 5334
Please have a look at this gist page that I have provided for the full source code https://gist.github.com/SamanShafigh/a0fbc2483e75dc4d6f82ca534a6174d4
So let assume you have 4 components called D1, D2, D3. What you need is to create a dependency injection and a dependency container mechanism. This is a very simple implementation
Imagine you have a config file like this that defines your components
export default [
{
name:'D1',
path:'D1'
},
{
name:'D2',
path:'D2'
},
{
name:'D3',
path:'D3'
}];
Then you can have a component container something like this
import componentsConfig from 'ComponentsConfig';
let components = {};
for (var i = 0; i < componentsConfig.length; i++) {
let componentConfig = componentsConfig[i];
// Check if component is not already loaded then load it
if (components[componentConfig.name] === undefined) {
components[componentConfig.name] = require(`${componentConfig.path}`).default;
}
}
export default components;
Finally in the place you want to load your components you can use your component container to load your components dynamically or in other word you can inject your components
import React, { Component } from 'react';
import ComponentContainer from './ComponentContainer';
class App extends Component {
render() {
let components = ['D1', 'D2', 'D3'];
return (
<div>
<h2>Dynamic Components Loading</h2>
{components.map((componentId) => {
let Component = ComponentContainer[componentId];
return <Component>{componentId}</Component>;
})}
</div>
);
}
}
export default App;
Upvotes: 8
Reputation: 2630
Basically it boils down to pre-creating all the chunks you will ever need. Then you just need a way to dynamically refer to them. Here's the solution I based mine on:
http://henleyedition.com/implicit-code-splitting-with-react-router-and-webpack
and here's what I do since I don't use React Router (side note: i dont find it to be a good match for redux or animations):
//loader:
{
test: (folder)\/.*\.js,
include: path.resolve(__dirname, 'src')
loader: ['lazy?bundle', 'babel']
}
//dynamic usage within React component:
const My_COMPONENTS = {
ComponentA: require('./folder/ComponentA'),
ComponentB: require('./folder/ComponentB'),
}
class ParentComponent extends React.Component {
componentDidMount() {
My_COMPONENTS[this.props.name](component => this.setState({component}));
}
render() {
return <this.state.component />;
}
}
So the result is you are dynamically rendering a component, but from a static pre-determined set of possibilities--all while, only sending no more to the client than the chunks the visitor is actually interested in.
ALSO, here's a component I have that does this well:
import React from 'react';
import Modal from './widgets/Modal';
export default class AjaxModal extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
Content: null
};
}
componentDidMount() {
if(this.props.show) {
this.loadContent();
}
}
componentWillReceiveProps({show}) {
if(show && !this.state.Content) {
this.loadContent(1200); //dont interfere with animation
}
}
loadContent(ms=0) {
setTimeout(() => {
this.props.requestLazyBundle(({default: Content}) => {
this.setState({Content});
});
}, ms);
}
render() {
let {Content} = this.state;
return (
<Modal title={this.props.title} {...this.props} loading={!Content}>
{Content ? <Content /> : null}
</Modal>
);
}
}
pass pass an async require bundler function as this.props.requestLazybundle
, like this:
render() {
let requestLazyBundle = require('bundle?lazy&name=AnotherComponent!../content/AnotherComponent');
return (
<AjaxModal title='Component Name' {...props} requestLazyBundle={requestLazyBundle} />
);
}
Upvotes: 23