Dominik
Dominik

Reputation: 437

How to dynamically create components with Tags?

I want to dynamically create JSX tags for imported components. So my idea is having something like this:

import DemoComponent from './DemoComponent';

class DynamicRendering extends Component {

  assembleResult() {
    const {
      democomponent
    } = this.props;

    const result = [];

    if (democomponent) {
      const Tag = `DemoComponent`;
      result.push(<Tag />);
    }

    return result;
  }

  render() {
    const result = this.assembleResult();
    return result;
  }
}

The idea is that I can pass a couple of different props to the component and then the component dynamically crates JSX tags and assembles them together. The reason I want this because I have about 15 components I want to render dynamically. Instead of implicitly writing them all I would prefer to make a loop over them and create them dynamically if needed. That way I can keep this component DRY.

The problem with the code above is that if you create a Tag like this, it will take it as a HTML element. This causes an error because there are no such HTML elements like 'DemoComponent'. I managed to solve this problem by creating a mapping of the name of the props to the component which should get loaded. See example below:

import DemoComponent from './DemoComponent';

const PROP_MODULE_MAP = new Map([
  ['democomponent', DemoComponent]
]);

class DynamicRendering extends Component {

  assembleResult() {
    const {
      democomponent
    } = this.props;

    const result = [];

    if (democomponent) {
      const Tag = PROP_MODULE_MAP.get('democomponent');
      result.push(<Tag />);
    }

    return result;
  }

  render() {
    const result = this.assembleResult();
    return result;
  }
}

But I was wondering if there was a simpler way then creating this Map. Is there another way how you can dynamically create JSX tags which represent a imported component?

Upvotes: 0

Views: 174

Answers (2)

Ivan Semochkin
Ivan Semochkin

Reputation: 8897

I don't know what is your project layout, but I suggest you could do something like this:
create a file for dynamic component import in your components folder:

import Comp1 from './Comp1'
import Comp2 from './Comp2'

export default { Comp1, Comp2 }

create a helper for function for dynamic rendering:

import components from './components/dynamic'

const renderComponents = compNames => {
    // take compNames as a list
    const compsToRender = compNames.map(name => components[name])
    // get all components by compNames provided and return them in array
    return compsToRender.map(Component => <Component />)
}

Then call it where you want .

<App>
    {renderComponents(['Comp1', 'Comp2'])}
</App>

If you want to pass props with component names, you could pass objects instead of strings and pass those in components inside the function, but I don't see why it will be better then just use plain components with props

Upvotes: 0

trixn
trixn

Reputation: 16309

You can just let the parent pass the desired component type:

Parent.js:

import SomeComponent from './someComponent';
import Child from './child';

// the parent renders Child and passes the type SomeComponent as a prop
const Parent = () => <Child Elem={SomeComponent} />

Child.js:

// the Child renders the component type passed
// note that the prop "Elem" is capitalized so that it will not be treated as a html node
const Child = ({Elem}) => <Elem />;

export default Child;

This way the Child component is capable of rendering any component type that it gets passed. This is much more flexible and does not require the Child to know all the components it should render at compile time.

Note that when rendering the passed component type in the child the variable to render has to be capitalized or it will be treated as a usual html node. See User-Defined Components Must Be Capitalized for details.

If you do not want the prop name to be capitalized you can reassign the value to a capitalized name in the child before rendering it:

const Child = ({elem: Elem}) => <Elem />;    

Upvotes: 1

Related Questions