dietary-wheevil
dietary-wheevil

Reputation: 4481

How do I dynamically generate an element with a tag name based on a prop?

I'm interested in building a reusable <Element> React component for my particular purposes. The intent is for whomever using it to optionally specify a tagName prop that dictates the exact HTML element into which the component will compile. As it stands, here is what the Element component looks like:

import React, { Component } from 'react';    

class Element extends Component {
  constructor(props) {
    super(props);
    this.mapToElement = new Map([
      ['ARTICLE', (props, children) => (<article {...props}>{children}</article>)],
      ['ASIDE', (props, children) => (<aside {...props}>{children}</aside>)],
      ['DIV', (props, children) => (<div {...props}>{children}</div>)],
      ['FOOTER', (props, children) => (<footer {...props}>{children}</footer>)],
      ['HEADER', (props, children) => (<header {...props}>{children}</header>)],
      ['MAIN', (props, children) => (<main {...props}>{children}</main>)],
      ['P', (props, children) => (<p {...props}>{children}</p>)],
      ['SECTION', (props, children) => (<section {...props}>{children}</section>)],
      ['SPAN', (props, children) => (<span {...props}>{children}</span>)]
    ]);
  }

  render() {
    const {
      children,
      tagName = 'DIV',
      ...rest
    } = this.props;

    return (this.mapToElement.get(tagName.toUpperCase())(rest, children));
  }
};

As can be seen, however, the ability for this component to successfully work is driven by a rather verbose mapping of HTML tagNames to their corresponding JSX syntax. I'd optimally like to make this component have support for all non-self-closing HTML elements, but would obviously like to do so without being forced to expand this mapping to include every possible HTML element fitting that criterion.

Is there a more dynamic, future-proof means for doing so and, if so, what might that look like?

Upvotes: 1

Views: 476

Answers (1)

Andrew Li
Andrew Li

Reputation: 57964

If I understand you correctly, you're just creating a wrapper for React.createElement which is what all JSX is transpiled into to create your elements:

render() {
  const {
    children,
    tagName = 'div',
    ...rest
  } = this.props;

  return React.createElement(tagName, rest, children);
}

Thus:

<Element tagName="span" foo="bar">
  Foobar!
</Element>

Will become (once transpiled):

React.createElement('span', {
  foo: 'bar'
}, 'Foobar!');

Which is the same as:

<span foo="bar">Foobar!</span>

And if you only ever wanted to restrict it to just DOM elements and not custom React components, just restrict the proptype or validate the type yourself:

tagName: PropType.string.isRequired

Or even your own validator:

tagName: (props, propName, componentName) => {
  if(typeof props[propName] !== 'string') {
    return new Error(`The ${propName} prop should be a string in ${componentName}`);
  }
}

Upvotes: 1

Related Questions