React / JSX Dynamic Component Name React / JSX Dynamic Component Name reactjs reactjs

React / JSX Dynamic Component Name


<MyComponent /> compiles to React.createElement(MyComponent, {}), which expects a string (HTML tag) or a function (ReactClass) as first parameter.

You could just store your component class in a variable with a name that starts with an uppercase letter. See HTML tags vs React Components.

var MyComponent = Components[type + "Component"];return <MyComponent />;

compiles to

var MyComponent = Components[type + "Component"];return React.createElement(MyComponent, {});


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.jsexport class Foo extends React.Component { ... }// dynamic-components.jsexport * from './Foo';export * from './Bar';// some module that uses dynamic componentimport * 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;}...@dynamicclass 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.