React's component-driven approach makes it easy for us to build dynamic UI components. Have you ever wondered how CMSs like Wordpress and Drupal build UI dynamically with page builders? Well, that's what we are going to learn in this guide; of course, we’ll be working in the context of development in the React Ecosystem.
For this guide, we are going to explore using a CMS Rest API that will provide a "JSON-ified" version of our UI. More specifically, we are going to build a form and its components dynamically.
The following would be the JSON response from the server:
1{
2 "form_heading": "Contact Form",
3 "fields": [
4 {
5 "label": {
6 "text": "Name"
7 },
8 "input": {
9 "type": "text",
10 "name": "name",
11 "validations": {
12 "required": true,
13 "minlength": 3,
14 "maxlength": 60
15 }
16 }
17 },
18 {
19 "label": {
20 "text": "Email"
21 },
22 "input": {
23 "type": "email",
24 "name": "email",
25 "validations": {
26 "required": true
27 }
28 }
29 },
30 {
31 "label": {
32 "text": "Message"
33 },
34 "input": {
35 "type": "textarea",
36 "name": "message",
37 "validations": {
38 "required": true,
39 "minlength": 30,
40 "maxlength": 255
41 }
42 }
43 }
44 ],
45 "submit_button": {
46 "text": "Submit"
47 }
48}
Run the following command to create a React project with create-react-app:
1create-react-app dynamic-form
Create a <ContactForm />
component inside the src folder and add the following code to fetch the data from the server. If you are unfamiliar with the browser Fetch API, you can find additional information my previous guide on All You Need to Know About Axios.
1const SERVER_URL = "https://61m46.sse.codesandbox.io"; // enter your server URL here
2
3class ContactForm extends Component {
4 constructor(props) {
5 super(props);
6 this.state = {
7 form: {}
8 };
9 }
10
11 componentDidMount() {
12 fetch(`${SERVER_URL}/forms/1`)
13 .then(res => res.json())
14 .then(data => this.setState({ forms: data }));
15 }
16
17 render() {
18 return <div>{JSON.stringify(this.state.forms)}</div>;
19 }
20}
Let's create a utility function that will render the input fields for us based on the field type.
1renderField = field => {
2 const { input } = field;
3 const { validations } = input;
4 switch (field.input.type) {
5 case "textarea":
6 return (
7 <textarea
8 type={input.type}
9 name={input.name}
10 onChange={this.handleInput}
11 value={this.state[field.name]}
12 minLength={validations.minlength}
13 maxLength={validations.maxlength}
14 required={validations.required}
15 rows={input.rows}
16 id={input.name}
17 />
18 );
19 default:
20 return (
21 <input
22 type={input.type}
23 name={input.name}
24 onChange={this.handleInput}
25 value={this.state[field.name]}
26 minLength={validations.minlength}
27 maxLength={validations.maxlength}
28 required={validations.required}
29 id={input.name}
30 />
31 );
32 }
33};
We are using the switch
and case
statements to return the respective field according to the field input type specified in the JSON response.
For handling the input changes, we will write a onChange
handler function that will store the input values in the state as a key-value pair where the key would be the name of the input field.
1class ContactForm extends Component {
2 // ...
3 handleInput = e =>
4 this.setState({ [e.currentTarget.name]: e.currentTarget.value });
5
6 render() {
7 // ...
8 }
9}
In the render()
function of the component we will loop through each field and render it using the renderField()
utility function.
1class ContactForm extends Component {
2 // ...
3
4 render() {
5 const { form } = this.state;
6 if (Object.keys(form).length < 1) {
7 return "Loading...";
8 }
9 return (
10 <>
11 <h1>{form.form_heading}</h1>
12 <div>
13 <form onSubmit={() => console.log(this.state)}>
14 {form.fields.map(field => (
15 <div className="input-group" key={field.input.name}>
16 <label for={field.input.name}>{field.label.text}</label>
17 {this.renderField(field)}
18 </div>
19 ))}
20 <button type="submit">{form.submit_button.text}</button>
21 </form>
22 </div>
23 </>
24 );
25 }
26}
1import ContactForm from "./ContactForm";
2
3function App() {
4 return (
5 <div className="App">
6 <ContactForm />
7 </div>
8 );
9}
1import React, { Component } from "react";
2
3const SERVER_URL = "https://61m46.sse.codesandbox.io"; // enter your server URL here
4
5class ContactForm extends Component {
6 constructor(props) {
7 super(props);
8 this.state = {
9 form: {}
10 };
11 }
12
13 componentDidMount() {
14 fetch(`${SERVER_URL}/forms/1`)
15 .then(res => res.json())
16 .then(data => this.setState({ form: data }));
17 }
18
19 handleInput = e =>
20 this.setState({ [e.currentTarget.name]: e.currentTarget.value });
21
22 renderField = field => {
23 const { input } = field;
24 const { validations } = input;
25 switch (field.input.type) {
26 case "textarea":
27 return (
28 <textarea
29 type={input.type}
30 name={input.name}
31 onChange={this.handleInput}
32 value={this.state[field.name]}
33 minLength={validations.minlength}
34 maxLength={validations.maxlength}
35 required={validations.required}
36 rows={input.rows}
37 id={input.name}
38 />
39 );
40 default:
41 return (
42 <input
43 type={input.type}
44 name={input.name}
45 onChange={this.handleInput}
46 value={this.state[field.name]}
47 minLength={validations.minlength}
48 maxLength={validations.maxlength}
49 required={validations.required}
50 id={input.name}
51 />
52 );
53 }
54 };
55
56 render() {
57 const { form } = this.state;
58 if (Object.keys(form).length < 1) {
59 return "Loading...";
60 }
61 return (
62 <>
63 <h1>{form.form_heading}</h1>
64 <div>
65 <form onSubmit={() => console.log(this.state)}>
66 {form.fields.map(field => (
67 <div className="input-group" key={field.input.name}>
68 <label for={field.input.name}>{field.label.text}</label>
69 {this.renderField(field)}
70 </div>
71 ))}
72 <button type="submit">{form.submit_button.text}</button>
73 </form>
74 </div>
75 </>
76 );
77 }
78}
79
80export default ContactForm;
1import React from "react";
2import ReactDOM from "react-dom";
3import ContactForm from "./ContactForm";
4
5import "./styles.css";
6
7function App() {
8 return (
9 <div className="App">
10 <ContactForm />
11 </div>
12 );
13}
14
15const rootElement = document.getElementById("root");
16ReactDOM.render(<App />, rootElement);
1.App {
2 font-family: sans-serif;
3}
4
5.input-group {
6 margin-bottom: 10px;
7}
8
9.input-group label {
10 display: block;
11 margin-bottom: 5px;
12}
13
14button {
15 border: none;
16 padding: 8px 24px;
17}
Let's have a look at how we can dynamically create a select input field.
For the select field, we can assume the following JSON response:
1{
2 "label": {
3 "text": "Role"
4 },
5 "input": {
6 "type": "select",
7 "name": "role",
8 "options": [
9 {
10 "value": "frontend-developer",
11 "text": "Frontend Developer"
12 },
13 {
14 "value": "backend-developer",
15 "text": "Backend Developer"
16 },
17 {
18 "value": "fullstack-developer",
19 "text": "Fullstack Developer"
20 }
21 ]
22 }
23}
To render the select field, we will loop through the options using the map()
function.
Lets update our renderField()
method:
1renderField = field => {
2 const { input } = field;
3 const { validations } = input;
4 switch (field.input.type) {
5 case "textarea":
6 return (
7 <textarea
8 type={input.type}
9 name={input.name}
10 onChange={this.handleInput}
11 value={this.state[field.name]}
12 minLength={validations.minlength}
13 maxLength={validations.maxlength}
14 required={validations.required}
15 rows={input.rows}
16 id={input.name}
17 />
18 );
19 // This case is for rendering the select field
20 case "select":
21 return (
22 <select name={input.name}>
23 {input.options.map(option => (
24 <option value={option.value}>{option.text}</option>
25 ))}
26 </select>
27 );
28 default:
29 return (
30 <input
31 type={input.type}
32 name={input.name}
33 onChange={this.handleInput}
34 value={this.state[field.name]}
35 minLength={validations.minlength}
36 maxLength={validations.maxlength}
37 required={validations.required}
38 id={input.name}
39 />
40 );
41 }
42};
I hope you liked this guide on creating UI dynamically with React. Until next time, goodbye and don't forget to code like a beast.