Skip to content

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

How to Construct a UI Dynamically

Nov 21, 2019 • 10 Minute Read

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:

      {
  "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"
  }
}
    

Start the Project with create-react-app

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

      create-react-app dynamic-form
    

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.

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

Function to Render the Field

Let's create a utility function that will render the input fields for us based on the field type.

      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}
        />
      );
  }
};
    

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.

      class ContactForm extends Component {
  // ...
  handleInput = e =>
    this.setState({ [e.currentTarget.name]: e.currentTarget.value });

  render() {
    // ...
  }
}
    

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.

      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>
      </>
    );
  }
}
    

Import the Form Component in the index.js File

      import ContactForm from "./ContactForm";

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

Complete Code

ContactForm.js File

      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;
    

index.js file

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

styles.css file

      .App {
  font-family: sans-serif;
}

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

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

button {
  border: none;
  padding: 8px 24px;
}
    

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:

      {
  "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"
      }
    ]
  }
}
    

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

Lets update our renderField() method:

      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}
        />
      );
  }
};
    

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.