[javascript] React / JSX Dynamic Component Name

I am trying to dynamically render components based on their type.

For example:

var type = "Example";
var ComponentName = type + "Component";
return <ComponentName />; 
// Returns <examplecomponent />  instead of <ExampleComponent />

I tried the solution proposed here React/JSX dynamic component names

That gave me an error when compiling (using browserify for gulp). It expected XML where I was using an array syntax.

I could solve this by creating a method for every component:

newExampleComponent() {
    return <ExampleComponent />;
}

newComponent(type) {
    return this["new" + type + "Component"]();
}

But that would mean a new method for every component I create. There must be a more elegant solution to this problem.

I am very open to suggestions.

This question is related to javascript reactjs react-jsx higher-order-components

The answer is


Edit: Other answers are better, see comments.

I solved the same problem this way:

...
render : function () {
  var componentToRender = 'component1Name';
  var componentLookup = {
    component1Name : (<Component1 />),
    component2Name : (<Component2 />),
    ...
  };
  return (<div>
    {componentLookup[componentToRender]}
  </div>);
}
...

Suspose we wish to access various views with dynamic component loading.The following code gives a working example of how to accomplish this by using a string parsed from the search string of a url.

Lets assume we want to access a page 'snozberrys' with two unique views using these url paths:

'http://localhost:3000/snozberrys?aComponent'

and

'http://localhost:3000/snozberrys?bComponent'

we define our view's controller like this:

import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import {
  BrowserRouter as Router,
  Route
} from 'react-router-dom'
import AComponent from './AComponent.js';
import CoBComponent sole from './BComponent.js';

const views = {
  aComponent: <AComponent />,
  console: <BComponent />
}

const View = (props) => {
  let name = props.location.search.substr(1);
  let view = views[name];
  if(view == null) throw "View '" + name + "' is undefined";
  return view;
}

class ViewManager extends Component {
  render() {
    return (
      <Router>
        <div>
          <Route path='/' component={View}/>
        </div>
      </Router>
    );
  }
}

export default ViewManager

ReactDOM.render(<ViewManager />, document.getElementById('root'));

There is an official documentation about how to handle such situations is available here: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime

Basically it says:

Wrong:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
    photo: PhotoStory,
    video: VideoStory
};

function Story(props) {
    // Wrong! JSX type can't be an expression.
    return <components[props.storyType] story={props.story} />;
}

Correct:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
    photo: PhotoStory,
    video: VideoStory
};

function Story(props) {
    // Correct! JSX type can be a capitalized variable.
    const SpecificStory = components[props.storyType];
    return <SpecificStory story={props.story} />;
}

There should be a container that maps component names to all components that are supposed to be used dynamically. Component classes should be registered in a container because in modular environment there's otherwise no single place where they could be accessed. Component classes cannot be identified by their names without specifying them explicitly because function name is minified in production.

Component map

It can be plain object:

class Foo extends React.Component { ... }
...
const componentsMap = { Foo, Bar };
...
const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;

Or Map instance:

const componentsMap = new Map([[Foo, Foo], [Bar, Bar]]);
...
const DynamicComponent = componentsMap.get(componentName);

Plain object is more suitable because it benefits from property shorthand.

Barrel module

A barrel module with named exports can act as such map:

// Foo.js
export class Foo extends React.Component { ... }

// dynamic-components.js
export * from './Foo';
export * from './Bar';

// some module that uses dynamic component
import * as componentsMap from './dynamic-components';

const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;

This works well with one class per module code style.

Decorator

Decorators can be used with class components for syntactic sugar, this still requires to specify class names explicitly and register them in a map:

const componentsMap = {};

function dynamic(Component) {
  if (!Component.displayName)
    throw new Error('no name');

  componentsMap[Component.displayName] = Component;

  return Component;
}

...

@dynamic
class Foo extends React.Component {
  static displayName = 'Foo'
  ...
}

A decorator can be used as higher-order component with functional components:

const Bar = props => ...;
Bar.displayName = 'Bar';

export default dynamic(Bar);

The use of non-standard displayName instead of random property also benefits debugging.


For a wrapper component, a simple solution would be to just use React.createElement directly (using ES6).

import RaisedButton from 'mui/RaisedButton'
import FlatButton from 'mui/FlatButton'
import IconButton from 'mui/IconButton'

class Button extends React.Component {
  render() {
    const { type, ...props } = this.props

    let button = null
    switch (type) {
      case 'flat': button = FlatButton
      break
      case 'icon': button = IconButton
      break
      default: button = RaisedButton
      break
    }

    return (
      React.createElement(button, { ...props, disableTouchRipple: true, disableFocusRipple: true })
    )
  }
}

Having a map doesn't look good at all with a large amount of components. I'm actually surprised that no one has suggested something like this:

var componentName = "StringThatContainsComponentName";
const importedComponentModule = require("path/to/component/" + componentName).default;
return React.createElement(importedComponentModule); 

This one has really helped me when I needed to render a pretty large amount of components loaded in a form of json array.


With the introduction of React.lazy, we can now use a true dynamic approach to import the component and render it.

import React, { lazy, Suspense } from 'react';

const App = ({ componentName, ...props }) => {
  const DynamicComponent = lazy(() => import(`./${componentName}`));
    
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <ComponentName {...props} />
    </Suspense>
  );
};

This approach makes some assumptions about the file hierarchy of course and can make the code easy to break.


Assume we have a flag, no different from the state or props:

import ComponentOne from './ComponentOne';
import ComponentTwo from './ComponentTwo';

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

With flag Compo fill with one of ComponentOne or ComponentTwo and then the Compo can act like a React Component.


If your components are global you can simply do:

_x000D_
_x000D_
var nameOfComponent = "SomeComponent";_x000D_
React.createElement(window[nameOfComponent], {});
_x000D_
_x000D_
_x000D_


Across all options with component maps I haven't found the simplest way to define the map using ES6 short syntax:

import React from 'react'
import { PhotoStory, VideoStory } from './stories'

const components = {
    PhotoStory,
    VideoStory,
}

function Story(props) {
    //given that props.story contains 'PhotoStory' or 'VideoStory'
    const SpecificStory = components[props.story]
    return <SpecificStory/>
}

I used a bit different Approach, as we always know our actual components so i thought to apply switch case. Also total no of component were around 7-8 in my case.

getSubComponent(name) {
    let customProps = {
       "prop1" :"",
       "prop2":"",
       "prop3":"",
       "prop4":""
    }

    switch (name) {
      case "Component1": return <Component1 {...this.props} {...customProps} />
      case "Component2": return <Component2 {...this.props} {...customProps} />
      case "component3": return <component3 {...this.props} {...customProps} />

    }
  }

I figured out a new solution. Do note that I am using ES6 modules so I am requiring the class. You could also define a new React class instead.

var components = {
    example: React.createFactory( require('./ExampleComponent') )
};

var type = "example";

newComponent() {
    return components[type]({ attribute: "value" });
}

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to reactjs

Error: Node Sass version 5.0.0 is incompatible with ^4.0.0 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Template not provided using create-react-app How to resolve the error on 'react-native start' Element implicitly has an 'any' type because expression of type 'string' can't be used to index Invalid hook call. Hooks can only be called inside of the body of a function component How to style components using makeStyles and still have lifecycle methods in Material UI? React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? Unable to load script.Make sure you are either running a Metro server or that your bundle 'index.android.bundle' is packaged correctly for release

Examples related to react-jsx

Best practice when adding whitespace in JSX Render Content Dynamically from an array map function in React Native Warning: Failed propType: Invalid prop `component` supplied to `Route` How to pass props to {this.props.children} Expected corresponding JSX closing tag for input Reactjs Why calling react setState method doesn't mutate the state immediately? Can you force a React component to rerender without calling setState? React / JSX Dynamic Component Name How to loop and render elements in React.js without an array of objects to map? ReactJS: "Uncaught SyntaxError: Unexpected token <"

Examples related to higher-order-components

Functions are not valid as a React child. This may happen if you return a Component instead of from render How do I conditionally add attributes to React components? Delayed rendering of React components Can you force a React component to rerender without calling setState? Hide/Show components in react native How to print React component on click of a button? React / JSX Dynamic Component Name Update style of a component onScroll in React.js react-router - pass props to handler component React component not re-rendering on state change