Using Dynamic Element Names With The JSX Transpiler In ReactJS
When I first started using JSX with ReactJS, I knew that you could use native HTML elements, like <a> and <span>, as well as React Components, like <List> and <ListItem>; but, it didn't occur to me that JSX element names were dynamic in a more general sense. Mostly by trial-and-error, I discovered that a JSX element could be defined by any variable that didn't look like a native HTML element.
Run this demo in my JavaScript Demos project on GitHub.
The JSX transpiler, in ReactJS, uses upper-case and lower-case convention to distinguish between HTML elements and React components; React components are upper-case and HTML elements are lower-case. But, if you look at the interactive JSX compiler, I think the breakdown can be worded a bit differently: lower-case values become inline strings and everything else becomes a variable reference.
Once I realized that things like "<div>" and "<span>" just compiled down to React.createElement("div") calls, it made sense that the "div" argument could be replaced with a variable that contained a string that represented an element name.
To see this in action, I've created a simple ReactJS component that cycles through a list of HTML element types. And, as it cycles through, the current element type is stored in the component state and used to render the component element:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
Using Dynamic Element Names With The JSX Transpiler In ReactJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body>
<h1>
Using Dynamic Element Names With The JSX Transpiler In ReactJS
</h1>
<div id="content">
<!-- App will be rendered here. -->
</div>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/reactjs/react-0.13.3.js"></script>
<script type="text/javascript" src="../../vendor/reactjs/JSXTransformer-0.13.3.js"></script>
<script type="text/jsx">
// I manage the root component.
var Demo = React.createClass({
// I return the initial state of the component.
getInitialState: function() {
this.tagNames = [ "div", "p", "article", "section", "header", "footer" ];
return({
tagName: "div"
});
},
// ---
// PUBLIC METHODS.
// ---
// I run once, on the client, when the component has been mounted on the DOM.
componentDidMount: function() {
// Now that we have access to the window, let's start changing the
// current tag every second.
setInterval( this.cycleTagName, 1000 );
},
// I return the virtual DOM represented by the current component state.
render: function() {
// We are using the variable, this.state.tagName, to define the element
// in this component. As long as our variable name doesn't conflict with
// one of the known DOM elements (ex, "<a>"), the JSX transpiler will
// happily create a dynamic tag.
// --
// NOTE: I could have stored this value in an intermediary value that was
// easier to read, like "Container", and it would have worked as well.
return(
<this.state.tagName>
Holy chickens, this is a <{ this.state.tagName }> tag!
</this.state.tagName>
);
},
// ---
// PRIVATE METHODS.
// ---
// I cycle through to the next tag name in the collection.
cycleTagName: function() {
var index = this.tagNames.indexOf( this.state.tagName );
var nextTag = ( this.tagNames[ ++index ] || this.tagNames[ 0 ] );
this.setState({
tagName: nextTag
});
}
});
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// Render the root Demo and mount it inside the given element.
React.render( <Demo />, document.getElementById( "content" ) );
</script>
</body>
</html>
As you can see, every second, I'm cycling through to the next HTML tag using a setInterval(). Since I am storing the current tag using setState(), ReactJS knows to re-render the current component, which will re-render the component element using the dynamic tag name. And, when we run this code, we get the following output:
I was using this functionality in my previous post on attaching dynamic event handlers to proxied child elements, in ReactJS. In that particular post, the functionality was used as a means to drive home the very dynamic nature of event bindings. But, I could definitely see a use-case for this level of dynamic tag generation in a real app; and, it's good to know that it's possible.
Want to use code from this post? Check out the license.
Reader Comments
Huh, somehow I missed this and went with the cumbersome React.createElement syntax: https://gitlab.com/vanity/vanity/blob/master/src/scripts/components/VanityApp.jsx#L55
Thanks for the tip :)
@Vincent,
Ah, I see - it took me a minute to figure out what you meant. You were using React.createElement() so that you could pass "elementName" dynamically. Ultimately, that is what the JSX compiles down to; so, you were on the money - this is just an alternate syntax.
How would you add start and stop timers to cycling? For example, add two buttons that will start and stop the cycling of the tags. I've been playing around with it with no luck.
@Shojib,
I forgot to mention that. If you have the buttons in a different component, use a mixin to have the timer functions which could be shared with both components.