React - Dynamically Import Components
Asked Answered
I

9

42

I have a page which renders different components based on user input. At the moment, I have hard coded the imports for each component as shown below:

    import React, { Component } from 'react'
    import Component1 from './Component1'
    import Component2 from './Component2'
    import Component3 from './Component3'

    class Main extends Component {
        render() {
            var components = {
                'Component1': Component1,
                'Component2': Component2,
                'Component3': Component3
            };
            var type = 'Component1';  // just an example
            var MyComponent = Components[type];
            return <MyComponent />
        }
    }

    export default Main

However, I change/add components all the time. Is there a way to perhaps have a file which stores ONLY the names and paths of the components and these are then imported dynamically in another file?

Imago answered 15/1, 2018 at 18:10 Comment(0)
I
58

I think there may have been some confusion as to what I was trying to achieve. I managed to solve the issue I was having and have shown my code below which shows how I solved it.

Separate File (ComponentIndex.js):

    let Components = {};

    Components['Component1'] = require('./Component1').default;
    Components['Component2'] = require('./Component2').default;
    Components['Component3'] = require('./Component3').default;

    export default Components

Main File (Main.js):

    import React, { Component } from 'react';
    import Components from './ComponentIndex';

    class Main extends Component {
        render () {
            var type = 'Component1'; // example variable - will change from user input
            const ComponentToRender = Components[type];
            return <ComponentToRender/>
        }
    }

    export default Main

This method allows me to add/remove components very quickly as the imports are in one file and only requires changing one line at a time.

Imago answered 15/1, 2018 at 19:47 Comment(6)
The aim was to load in new components with as little change to the code as possible. This method requires an addition of only one line.Imago
You could also do export { default as Component1 } from './Component1' then import * as componentList from './ComponentIndex' then componentList[type].Gaye
why we are accessing with .default againGomel
Thanks a lot, this worked best for my problem. I was trying to render different components based on a config file, that is dynamic.Beberg
How would you do this in typescript? I've this error => Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ ComponentCar: () => Element; }'. No index signature with a parameter of type 'string' was found on type '{ ComponentCar: () => Element; }'.ts(7053) this comes from this command const DynamicComponent = Components[Component] where Component is CarSemifinalist
What to do if I have to pass loads of props to the component?Confront
P
32

Since the question is really old, the answers maybe were ok. But nowadays, if someone have the same problem should use dynamic import, in order to load only the component needed and avoid to load all the different ones.

const component = React.lazy(() => import('./component.jsx'));

try the example here: demo

Palumbo answered 24/12, 2019 at 20:28 Comment(2)
Nice answer, but it is worth noting that React.lazy is not yet available for server-side rendering.Garganey
And that it is not possible to use a fully dynamic import statement: webpack.js.org/api/module-methods/…Bestraddle
F
4
import React, { Component } from 'react'
import Component1 from './Component1'
import Component2 from './Component2'
import Component3 from './Component3'

class Main extends Component {
    render() {
        var type = 'Component1';  // just an example
        return (
          <div>
            {type == "Component1" && <Component1 />}
            {type == "Component2" && <Component2 />}
            ...
          </div>
        )
    }
}

export default Main

You can use conditional rendering insted. Hope it will help

Check this

Flyfish answered 15/1, 2018 at 18:17 Comment(2)
Simple approach! Solved my problem. I was trying to load the component when it was getting used. const Modal = lazy(() => import('./sharedmodule/common-modal/common-modal.component')); Above Modal chunk was getting loaded as soon as it was rendering on DOM. Because I was doing. return( <Modal showModal={popup}></Modal> // This was causing it render immediately )Deceit
but still, it imports component 1,2,3 and only the change is you just hide and show the componentForeignism
W
2

Here is another solution: We get the list of needed components list = ['c1', 'c2', 'c3']. It may be pulled from json file to an array (i use redux-store so i initiate getting forms by this.props.getForms()). But you may just create and access the list of components manually.

    componentDidMount = () => {
//we get elements list from any source to redux-store
        this.props.getForms();
//access redux-store to the list
        const forms = this.props.configBody.sets;
//make deep object copy
        const updatedState = { ...this.state };
        updatedState.modules = [];
        if (forms) {
//here is the very dynamic import magic: we map the import list and prepare to store the imports in Component`s state
            const importPromises = forms.map(p =>
                import(`../TemplateOrders/Template${p.order}`)
                    .then(module => {
                        updatedState.modules.push(module.default)
                    })
                    .catch(errorHandler(p))
            )
//wait till all imports are getting resolved
            Promise.all(importPromises)
                .then(res =>
//then run setState
                    this.setState({ ...updatedState }, () => {
                        console.log(this.state);
                    }))
        }
    }

    render() {
        const forms = this.props.configBody.sets;
//we iterate through the modules and React.createElemet`s 
        const list = this.state.modules
            ? this.state.modules.map((e, i) =>
                createElement(e, { key: forms[i].title }, null)
            )
            : [];
        return (
            <Fragment>
                <Link to='/'>Home</Link>
                <h1>hello there</h1>
//push them all to get rendered as Components
                {list.map(e => e)}
            </Fragment>
        )
    }

So when your app is loaded it pulls the needed modules.

I thought to use promises to import them, but modules are already promises.

In case we need to ajax them from server lately, so we need to split the modules before bundling with require (or something like that) dont know exactly.

Wame answered 5/6, 2019 at 8:37 Comment(0)
R
2

You can bundle your components as micro-apps and hot load them into your application from a url. Here is a poc that supports dynamically importing components and micro-apps from a route based on a configuration on the site level.

https://github.com/eschall/react-micro-frontend

Reduplicate answered 3/7, 2019 at 15:5 Comment(1)
I looked at your poc but since I never worked with redux I did not quite get the hang of it. I want to import a react component from an external api. The result can be preprocessed etc. I am just wondering if that is possible. I did ask this question here https://mcmap.net/q/391498/-how-to-use-a-react-component-fetched-from-an-api/6394630 Maybe you can have a look on itHowlyn
C
2

if we need to use dynamic import, in order to load only the component needed and avoid to load all the different ones. using code-splitting

(async () => {
  const { Component1 } = await import('./Component1');
})();
Capps answered 24/11, 2021 at 6:43 Comment(0)
W
1

One more way to make dynamic import without any promise:

import React from "react";
import ColumnSet1Brick from "./sets/column-set/column-set-1-brick";
import ColumnSet2Brick from "./sets/column-set/column-set-2-brick";
import ColumnSet3Brick from "./sets/column-set/column-set-3-brick";
import ColumnSet4Brick from "./sets/column-set/column-set-4-brick";

const setClasses = {
  ColumnSet1Brick,
  ColumnSet2Brick,
  ColumnSet3Brick,
  ColumnSet4Brick
};

export default class SetBrickStack extends React.Component {

  ...



  getColumnSetInstance = (n) => new (setClasses[`ColumnSet${n}Brick`])(this.paramFactory.getBasicProps());

  getBricksOnInit = () => {
    const columnSets = [1, 2, 3, 4];
    const bricksParams = columnSets.map(this.getColumnSetInstance);
    return bricksParams;
  };
}

the trick is that babel compiles the classes to another name such as react__WEBPACK_IMPORTED_MODULE_1___default so to access it we need to assign the compile module name in one object, that's why there is setClasses which compiles to object with references

const setClasses = {
  ColumnSet1Brick: react__WEBPACK_IMPORTED_MODULE_1___default,
  ColumnSet2Brick: react__WEBPACK_IMPORTED_MODULE_2___default,
  ColumnSet3Brick: react__WEBPACK_IMPORTED_MODULE_3___default,
  ColumnSet4Brick: react__WEBPACK_IMPORTED_MODULE_4___default
};

and you may import it as usual class names:

new (setClasses[`ColumnSet${n}Brick`])(parameters)
Wame answered 23/8, 2020 at 15:18 Comment(0)
T
0

you could create a component building function that utilizes React.createElement. this way you can import the function from a helper file. hard to show more code in this example without more information, but you can use state helpers from this file too if your goal is to completely remove the logic from this component.

class Main extends Component {

constructor(props) {
  super();
  this.state = { displayComponent: Component1 }
}

buildComponent = () => {
  // create element takes additional params for props and children
  return React.createElement( this.state.displayComponent )
}

render() {
    var type = 'Component1';  // just an example
    return (
      <div>
        { this.buildComponent() }
      </div>
    )
}

}

Tippets answered 15/1, 2018 at 18:35 Comment(0)
B
-6

You can use Route and Switch from 'react-router-dom' to dynamically render components based on the path. Here is the sample

render() {
return (
  <>
    <Header  />
    <BrowserRouter>
      <Switch>
        <Route path="/abc" exact render={() => (<Abc />)}/>
        <Route path="/abcd" exact render={() => (<Abcd {...this.props} />)}/>
        <Route path="/xyz" exact render={() => (<Xyz />)}/>
        
      </Switch>
    </BrowserRouter>
    <Footer /></>
);  }
Bifrost answered 18/11, 2020 at 18:56 Comment(1)
Selective render yes but not dynamic import :)Misconception

© 2022 - 2024 — McMap. All rights reserved.