Author avatar

Gaurav Singhal

How to Construct a UI Dynamically

Gaurav Singhal

  • Oct 13, 2019
  • 10 Min read
  • 28 Views
  • Oct 13, 2019
  • 10 Min read
  • 28 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, Drupal build UI dynamically with page builders? Well, that's what we are going to learn in this guide, of-course in the context of development in the React Ecosystem.

For this guide, we are going to consider that we are using a CMS Rest API that will provide a "JSONified" 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
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
{
  "form_heading": "Contact Form",
  "fields": [
    {
      "label": {
        "text": "Name"
      },
      "input": {
        "type": "text",
        "name": "name",
        "validations": {
          "required": true,
          "minlength": 3,
          "maxlength": 60
        }
      }
    },
    {
      "label": {
        "text": "Email"
      },
      "input": {
        "type": "email",
        "name": "email",
        "validations": {
          "required": true
        }
      }
    },
    {
      "label": {
        "text": "Message"
      },
      "input": {
        "type": "textarea",
        "name": "message",
        "validations": {
          "required": true,
          "minlength": 30,
          "maxlength": 255
        }
      }
    }
  ],
  "submit_button": {
    "text": "Submit"
  }
}
json

Start the project with create-react-app

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

1
create-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. I guess you must be knowing about the browser Fetch API by now, if not do follow my previous guide.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const SERVER_URL = "https://61m46.sse.codesandbox.io"; // enter your server URL here

class ContactForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {}
    };
  }

  componentDidMount() {
    fetch(`${SERVER_URL}/forms/1`)
      .then(res => res.json())
      .then(data => this.setState({ forms: data }));
  }

  render() {
    return <div>{JSON.stringify(this.state.forms)}</div>;
  }
}
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.

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
renderField = field => {
  const { input } = field;
  const { validations } = input;
  switch (field.input.type) {
    case "textarea":
      return (
        <textarea
          type={input.type}
          name={input.name}
          onChange={this.handleInput}
          value={this.state[field.name]}
          minLength={validations.minlength}
          maxLength={validations.maxlength}
          required={validations.required}
          rows={input.rows}
          id={input.name}
        />
      );
    default:
      return (
        <input
          type={input.type}
          name={input.name}
          onChange={this.handleInput}
          value={this.state[field.name]}
          minLength={validations.minlength}
          maxLength={validations.maxlength}
          required={validations.required}
          id={input.name}
        />
      );
  }
};
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.

1
2
3
4
5
6
7
8
9
class ContactForm extends Component {
  // ...
  handleInput = e =>
    this.setState({ [e.currentTarget.name]: e.currentTarget.value });

  render() {
    // ...
  }
}
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.

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
class ContactForm extends Component {
  // ...

  render() {
    const { form } = this.state;
    if (Object.keys(form).length < 1) {
      return "Loading...";
    }
    return (
      <>
        <h1>{form.form_heading}</h1>
        <div>
          <form onSubmit={() => console.log(this.state)}>
            {form.fields.map(field => (
              <div className="input-group" key={field.input.name}>
                <label for={field.input.name}>{field.label.text}</label>
                {this.renderField(field)}
              </div>
            ))}
            <button type="submit">{form.submit_button.text}</button>
          </form>
        </div>
      </>
    );
  }
}
jsx

Import the Form component in the index.js file

1
2
3
4
5
6
7
8
9
import ContactForm from "./ContactForm";

function App() {
  return (
    <div className="App">
      <ContactForm />
    </div>
  );
}
jsx

Complete Code

ContactForm.js file -

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import React, { Component } from "react";

const SERVER_URL = "https://61m46.sse.codesandbox.io"; // enter your server URL here

class ContactForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      form: {}
    };
  }

  componentDidMount() {
    fetch(`${SERVER_URL}/forms/1`)
      .then(res => res.json())
      .then(data => this.setState({ form: data }));
  }

  handleInput = e =>
    this.setState({ [e.currentTarget.name]: e.currentTarget.value });

  renderField = field => {
    const { input } = field;
    const { validations } = input;
    switch (field.input.type) {
      case "textarea":
        return (
          <textarea
            type={input.type}
            name={input.name}
            onChange={this.handleInput}
            value={this.state[field.name]}
            minLength={validations.minlength}
            maxLength={validations.maxlength}
            required={validations.required}
            rows={input.rows}
            id={input.name}
          />
        );
      default:
        return (
          <input
            type={input.type}
            name={input.name}
            onChange={this.handleInput}
            value={this.state[field.name]}
            minLength={validations.minlength}
            maxLength={validations.maxlength}
            required={validations.required}
            id={input.name}
          />
        );
    }
  };

  render() {
    const { form } = this.state;
    if (Object.keys(form).length < 1) {
      return "Loading...";
    }
    return (
      <>
        <h1>{form.form_heading}</h1>
        <div>
          <form onSubmit={() => console.log(this.state)}>
            {form.fields.map(field => (
              <div className="input-group" key={field.input.name}>
                <label for={field.input.name}>{field.label.text}</label>
                {this.renderField(field)}
              </div>
            ))}
            <button type="submit">{form.submit_button.text}</button>
          </form>
        </div>
      </>
    );
  }
}

export default ContactForm;
jsx

index.js file -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from "react";
import ReactDOM from "react-dom";
import ContactForm from "./ContactForm";

import "./styles.css";

function App() {
  return (
    <div className="App">
      <ContactForm />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
jsx

styles.css file -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.App {
  font-family: sans-serif;
}

.input-group {
  margin-bottom: 10px;
}

.input-group label {
  display: block;
  margin-bottom: 5px;
}

button {
  border: none;
  padding: 8px 24px;
}
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "label": {
    "text": "Role"
  },
  "input": {
    "type": "select",
    "name": "role",
    "options": [
      {
        "value": "frontend-developer",
        "text": "Frontend Developer"
      },
      {
        "value": "backend-developer",
        "text": "Backend Developer"
      },
      {
        "value": "fullstack-developer",
        "text": "Fullstack Developer"
      }
    ]
  }
}
json

To render the select field we will loop through the options using the map() function.

Lets update our renderField() method -

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
renderField = field => {
  const { input } = field;
  const { validations } = input;
  switch (field.input.type) {
    case "textarea":
      return (
        <textarea
          type={input.type}
          name={input.name}
          onChange={this.handleInput}
          value={this.state[field.name]}
          minLength={validations.minlength}
          maxLength={validations.maxlength}
          required={validations.required}
          rows={input.rows}
          id={input.name}
        />
      );
    // This case is for rendering the select field
    case "select":
      return (
        <select name={input.name}>
          {input.options.map(option => (
            <option value={option.value}>{option.text}</option>
          ))}
        </select>
      );
    default:
      return (
        <input
          type={input.type}
          name={input.name}
          onChange={this.handleInput}
          value={this.state[field.name]}
          minLength={validations.minlength}
          maxLength={validations.maxlength}
          required={validations.required}
          id={input.name}
        />
      );
  }
};
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.

2