Author avatar

Gaurav Singhal

How to Construct a UI Dynamically

Gaurav Singhal

  • Nov 21, 2019
  • 10 Min read
  • 10,768 Views
  • Nov 21, 2019
  • 10 Min read
  • 10,768 Views
Web Development
React

Introduction

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}
json

Start the Project with create-react-app

Run the following command to create a React project with create-react-app:

1create-react-app dynamic-form
console

Let's Create Our Form Component

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}
jsx

Function to Render the Field

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};
jsx

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}
jsx

Let's Render the Form

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}
jsx

Import the Form Component in the index.js File

1import ContactForm from "./ContactForm";
2
3function App() {
4  return (
5    <div className="App">
6      <ContactForm />
7    </div>
8  );
9}
jsx

Complete Code

ContactForm.js File

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;
jsx

index.js file

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);
jsx

styles.css file

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}
css

Bonus Section - Let's Add a Select Field

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}
json

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};
jsx

Conclusion

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.