I had implemented an Angular 4 dynamic DOM prototype using the Angular Dynamic Component Loader and wanted to do the same thing with React JS. After doing some research I found that it was not very obvious how to accomplish this.
By the time I was done there ended up being two functional components worth sharing:
- Dynamic component creation using JSX.
- JSON driven dynamic DOM generation.
Source code for the demo project is here: reactjs-dynamic-dom-generation
Try demo app live! (with CodeSandbox)
The project was created using create-react-app. The only other package added was axios for making the AJAX call to retrieve the JSON content.
Dynamic Component Creation
With JSX, dynamic content generation turned out to be pretty simple. The core piece of code is in DynamicComponent.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import React, { Component } from 'react'; /*eslint-disable */ import DefaultComponent from "./DefaultComponent" import TableComponent from "./TableComponent" import OutputComponent from "./OutputComponent" import SwitchComponent from "./SwitchComponent" import RangeComponent from "./RangeComponent" import GroupComponent from "./GroupComponent" /*eslint-enable */ import ComponentService from "../services/ComponentService"; class DynamicComponent extends Component{ render() { if (this.props.context === null) { return null; } this.component = ComponentService.getComponent(this.props.context.ui); return <this.component context={this.props.context} path={this.props.path}/> } } export default DynamicComponent; |
In the demo application, all available components register themselves via the ComponentService, which is just a singleton that maintains a simple hash map. For example:
1 |
ComponentService.addComponent('switch', SwitchComponent); |
As highlighted on lines 17-18, the desired React Component is first fetched from the ComponentService and then passed to JSX via <this.component ... />.
The JSX preprocessor converted this embedded HTML into Javascript with the 'React Element' type set to the passed Component along with the additional attributes. I.e. if the UI type was 'switch', the hard-coded HTML would have been <SwitchComponent ... /> which is a perfectly acceptable JSX template.
Voilà, we have created a dynamic DOM element!
Note that Vue.js applications using JSX can use the same technique except they pass a Vue Component instead.
JSON Driven Dynamic DOM Generation
In order to demonstrate dynamic DOM generation I have defined a simple UI JSON structure. The demo uses Bootstrap panels for the group and table elements and only implements a few components.
The UI JSON is loaded from the server when the application is started and drives the DOM generation. A DynamicComponent is passed a context (i.e. its associated JSON object) along with a path (see below). Each UI element has the following attributes:
- name: A unique name within the current control context. It is used to form the namespace-like path that allows this component to be globally identified.
- ui: The type of UI element (e.g. "output", "switch", etc.). This is mapped by the ComponentService to its corresponding React Component. If the UI type is not registered, the DefaultComponent is used.
- label: Label used on the UI.
- controls: (optional) For container components ("group", "table"), this is an array of contained controls.
- value: (optional) For value-based components.
- range: (optional) Specifies the min/max/step for the range component.
This structure can easily be extended to meet custom needs.
There are a number of implementation details that I'm not covering in this post. I think the demo application is simple enough that just examining and playing with the code should answer any questions. If not, please ask.
The example UI JSON file is here: example-ui.json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
{ "Controls": [ { "name": "nestedGroup", "ui": "group", "label": "Nested Groups", "controls": [ { "name": "outputControls", "ui": "table", "label": "Output Controls Table", "controls": [ { "name": "textOutput", "ui": "output", "label": "Text Output", "value": "text" }, { "name": "numericOutput", "ui": "output", "label": "Numeric Output", "value": 1000.2 } ] } ] }, { "name": "interactiveControls", "ui": "table", "label": "Interactive Controls", "controls": [ { "name": "switchControl", "ui": "switch", "label": "Switch Control", "value": false }, { "name": "rangeControl", "ui": "range", "label": "Range Control", "value": 8, "range": { "min": 4, "max": 20, "step": 0.5 } } ] } ] } |
The resulting output, including console logging from the switch and range components, looks like this.
This is, of course, a very minimal implementation that was designed to just demonstrate dynamic DOM generation. In particular, there is no UI event handling, data binding, or other interactive functionality that would be required to make a useful application.
Enjoy!
Thank you for posting the valuable information about the React JS Blogs.
React JS Online Training</a
Marvelous approach. Congrats on your way of (dynamic) thinking.