Marc
Marc

Reputation: 411

React - Create tag dynamically from ES6 template literal with JSX

I need to display a header element in a React render method where the level is dynamically set in the constructor:

class HeaderComponent extends React.Component {

    constructor(props){
        super(props);
        
        this._checkedDepth = Math.min(6, props.depth)
    }

    render(){
        return(<h{ this._checkedDepth }>{ this.props.name }</h{ this._checkedDepth }>)
    }
}

ReactDOM.render(
  <HeaderComponent name="Header 1" depth="2"/>,
  document.getElementById('app')
);
<div id="app"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

This should render <h2>Header 1</h2> with name="Header 1" and depth=2, but I get an error instead:

Uncaught Error: Cannot find module "./HeaderComponent"

What am I overlooking?

I'm using React 15.4.1, babel-preset-es2015 6.9.0, babel-preset-react 6.5.0 and running it in Chrome 55.

Upvotes: 5

Views: 4327

Answers (2)

Danziger
Danziger

Reputation: 21161

Maybe a bit too late, but you can create a component or tag dynamically without using React.createClass with JSX putting the tag name in a variable and using that variable as you would with any other component.

In your case, inside render, you should have something like:

const TitleTag = `h{ this._checkedDepth }>`;

return <TitleTag>{ this.props.name }</TitleTag>

Note the first character of that variable must be uppercase in order to let React know that's a React component, otherwise a tag with the exact same name (not value) of your variable will be inserted instead.

See https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized:

When an element type starts with a lowercase letter, it refers to a built-in component like <div> or <span> and results in a string 'div' or 'span' passed to React.createElement. Types that start with a capital letter like <Foo /> compile to React.createElement(Foo) and correspond to a component defined or imported in your JavaScript file.

We recommend naming components with a capital letter. If you do have a component that starts with a lowercase letter, assign it to a capitalized variable before using it in JSX.

There's no way to do this without creating that variable, so trying to do it on the fly as you do in your code will not work (compiler limitation).

Here's a fully working example:

class HeaderComponent extends React.Component {

  constructor(props) {
    super(props);
    
    const depth = Math.max(Math.min(parseInt(props.depth) || 1, 6), 1);
    
    this.state = { depth };
  }

  onClick() {
    let depth;
    
    do {
      depth = Math.floor(Math.random() * 6) + 1;
    } while(depth === this.state.depth);
    
    this.setState({ depth });
  }

  render() {    
    const Title = `h${ this.state.depth }`;
  
    return <Title className="title" onClick={ () => this.onClick() }>{ this.props.name }</Title>;
  }
}

ReactDOM.render(
  <HeaderComponent name="Click Me!" depth="1"/>,
  document.getElementById('app')
);
body { margin: 0; }

h1 { font-size: 4rem; }
h2 { font-size: 3.5rem; }
h3 { font-size: 3rem; }
h4 { font-size: 2.5rem; }
h5 { font-size: 2rem; }
h6 { font-size: 1.5rem; }

.title {
  margin: 0;
  padding: 0 .5rem;
  cursor: pointer;
  user-select: none;
}

.title:hover {
  background: #FFA;
}
<div id="app"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Upvotes: 4

T Mitchell
T Mitchell

Reputation: 1017

Each JSX element is just syntactic sugar for calling React.createElement(component, props, ...children). So, anything you can do with JSX can also be done with just plain JavaScript. - https://facebook.github.io/react/docs/react-without-jsx.html

So you can do something like this:

render() {
  return React.createElement(`h${this._checkedDepth}`, this.props)
}

Upvotes: 4

Related Questions