React makes it super easy to create and render components dynamically. It enables us to build large, fast web apps with modern JavaScript. It has scaled pretty well for the folks at Facebook, Instagram, and many others. One of the coolest features is that you don't have to use JSX, a template extension to JavaScript, if you don't want to.
In this guide, instead of using JSX to render the component, we will use React.createElement()
and build the component based on a JSON config. As an example for this guide, we'll use the Reactstrap library and render a card component.
Let's take a look at the JSON config data and its format.
1const CardConfig = {
2 component: "card",
3 children: [
4 {
5 component: "img",
6 src:
7 "https://images.pexels.com/photos/2877188/pexels-photo-2877188.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500"
8 },
9 {
10 component: "body",
11 children: [
12 {
13 component: "title",
14 children: "This is a title"
15 },
16 {
17 component: "subtitle",
18 children: "This is the subtitle"
19 },
20 {
21 component: "text",
22 children:
23 "Some quick example text to build on the card title and make up the bulk of the card's content."
24 },
25 {
26 component: "button",
27 children: "Click Me!"
28 }
29 ]
30 }
31 ]
32};
This JSON structure corresponds to how the components are to be nested and rendered on the page or screen. Notice that I have added a component key in the config so that we can map it to the respective Reactstrap component. There's also a children
key, which specifies all the child components for the current component. Its value can be either a string or an array of components.
Other than that, we can have additional keys that we want to pass to the component as a prop. For example, here, the src
key in the image
component will be given as a prop.
Ideally, the JSON config should be rendered as shown below.
1<div class="card">
2 <img
3 src="https://images.pexels.com/photos/2877188/pexels-photo-2877188.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500"
4 class="card-img"
5 />
6 <div class="card-body">
7 <div class="card-title">This is a title</div>
8 <div class="card-subtitle">This is the subtitle</div>
9 <p class="card-text">
10 Some quick example text to build on the card title and make up the bulk of
11 the card's content.
12 </p>
13 <button class="btn btn-secondary">Click Me!</button>
14 </div>
15</div>
To render the components based on the component
key in the JSON config, we first need to create an object that maps the components with the component
key.
1const KeysToComponentMap = {
2 card: Card,
3 img: CardImg,
4 text: CardText,
5 body: CardBody,
6 title: CardTitle,
7 subtitle: CardSubtitle,
8 button: Button
9};
As you can see, I have mapped the component
key with the corresponding Reactstrap component.
At the top, we will import all the required components.
1import {
2 Card,
3 CardImg,
4 CardText,
5 CardBody,
6 CardTitle,
7 CardSubtitle,
8 Button
9} from "reactstrap";
In the renderer()
function, we will first check if the key exists in the KeysToComponentMap
object.
1const renderer = config => {
2 if (typeof KeysToComponentMap[config.component] !== "undefined") {
3 // ..
4 }
5};
Next we will use the React.createElement()
function to render the component.
The React.createElement()
function creates and returns a new React element of the given type.
1React.createElement(
2 type,
3 [props],
4 [...children]
5);
It takes in three arguments: type
, props
, and children
.
The type here is the React component that we want to render; props
is the data or properties that we want to pass on to the component, and children
is another component that we want to pass between the DOM elements.
The JSX we write in React gets compiled to a React.createElement()
function call with the help of a babel compiler.
Let's take a look at a simple example.
1let WelcomeText = React.createElement(
2 "h1",
3 { style: { color: "blue" } },
4 "This is a welcome text"
5);
The above gets rendered in the DOM as
1<h1 style="color:blue">
2 This is a welcome text
3</h1>
Now that you understand how React.createElement()
method works, let's get back to our original quest.
Into the createElement
function, we will pass the component that we want to render as the first argument, i.e., KeysToComponentMap[config.component]
. Notice that here to access the object, we are using the bracket []
notation and not the dot .
notation.
1function renderer(config) {
2 if (typeof KeysToComponentMap[config.component] !== "undefined") {
3 return React.createElement(KeysToComponentMap[config.component]);
4 }
5}
The bracket []
notation allows us to dynamically access a property using a variable. Hence, it is easier to get the component this way, rather than having a switch case block to determine which component to render.
If you run the code we have so far, you will notice that the <Card />
component gets rendered onto the DOM. That means we are going in the right direction.
1<div class="card-container">
2 <div class="card"></div>
3</div>
Now, let's take this further and render the child components as well.
We need to check if the child components exist. Then map over each child component and pass it on to the renderer()
function, making it recursive.
1config.children && config.children.map(c => renderer(c));
This looks fine. Let's try it out.
1function renderer(config) {
2 if (typeof KeysToComponentMap[config.component] !== "undefined") {
3 return React.createElement(
4 KeysToComponentMap[config.component],
5 {},
6 {
7 config.children && config.children.map(c => renderer(c))
8 }
9 );
10 }
11}
Bummer! If you got the error config.children.map is not a function
, don't worry; that's because all children are not the type of an array, and we have not taken that into consideration. For instance, the button
component has children of string
type. So let's rewrite our condition.
1function renderer(config) {
2 if (typeof KeysToComponentMap[config.component] !== "undefined") {
3 return React.createElement(
4 KeysToComponentMap[config.component],
5 {},
6 {
7 config.children &&
8 (typeof config.children === "string"
9 ? config.children
10 : config.children.map(c => renderer(c)))
11 }
12 );
13 }
14}
Yay! We got the child components to render. But wait, we still have an image component that isn't showing up.
For that, we need to pass the src
as a property in the prop argument.
1function renderer(config) {
2 if (typeof KeysToComponentMap[config.component] !== "undefined") {
3 return React.createElement(
4 KeysToComponentMap[config.component],
5 {
6 src: config.src
7 },
8 {
9 config.children &&
10 (typeof config.children === "string"
11 ? config.children
12 : config.children.map(c => renderer(c)))
13 }
14 );
15 }
16}
There you go. We have successfully rendered a component dynamically from a JSON config.
In case you get stuck somewhere or finding it difficult to follow along, this section has the source code for your reference.
1import React from "react";
2import ReactDOM from "react-dom";
3
4import App from "./App";
5
6const rootElement = document.getElementById("root");
7ReactDOM.render(<App />, rootElement);
1import React from "react";
2import "./styles.css";
3import RenderCard from "./RenderCard";
4import "bootstrap/dist/css/bootstrap.min.css";
5
6const CardConfig = [
7 {
8 component: "card",
9 children: [
10 {
11 component: "img",
12 src:
13 "https://images.pexels.com/photos/2877188/pexels-photo-2877188.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500"
14 },
15 {
16 component: "body",
17 children: [
18 {
19 component: "title",
20 children: "This is a title"
21 },
22 {
23 component: "subtitle",
24 children: "This is the subtitle"
25 },
26 {
27 component: "text",
28 children:
29 "Some quick example text to build on the card title and make up the bulk of the card's content."
30 },
31 {
32 component: "button",
33 children: "Click Me!"
34 }
35 ]
36 }
37 ]
38 }
39];
40
41export default function App() {
42 return (
43 <div className="App">
44 <div className="card-container">
45 {CardConfig.map(config => RenderCard(config))}
46 </div>
47 </div>
48 );
49}
1import React from "react";
2import {
3 Card,
4 CardImg,
5 CardText,
6 CardBody,
7 CardTitle,
8 CardSubtitle,
9 Button
10} from "reactstrap";
11
12const KeysToComponentMap = {
13 card: Card,
14 img: CardImg,
15 text: CardText,
16 body: CardBody,
17 title: CardTitle,
18 subtitle: CardSubtitle,
19 button: Button
20};
21
22function renderer(config) {
23 if (typeof KeysToComponentMap[config.component] !== "undefined") {
24 return React.createElement(
25 KeysToComponentMap[config.component],
26 {
27 src: config.src
28 },
29 config.children &&
30 (typeof config.children === "string"
31 ? config.children
32 : config.children.map(c => renderer(c)))
33 );
34 }
35}
36
37export default renderer;
Creating and rendering components dynamically can be very helpful when the JSON config data is stored in a database and retrieved by the app during runtime. The downside to this approach is that the client or the browser must know which components are going to be used so that it can map with the React elements. Any change in the JSON schema will break the renderer()
function.
Nevertheless, this approach is widely used in popular CMS platforms like WordPress, and I'm pretty sure if you write it wisely, you can use it in your applications. That's it from this guide. I hope you learned something new today. Until next time, happy coding.