A React Material UI Library to Build Any Form With Only JSON Config
Tags: javascript, productivity, beginners, react
Published: 2021-11-12 15:26:13

Suppose we want to create a form to collect shipping address. Here's how we can replicate the form below with just JSON config and the open-source library I built [`@jeremyling/react-material-ui-form-builder`](https://github.com/jzcling/react-material-ui-form-builder).

{% codesandbox shipping-address-yjslw %}

### The JSON Config

```javascript
const fields = [
  {
    title: "Shipping Address",
    component: "display-text",
    titleProps: {
      style: {
        fontSize: 20,
        marginTop: 16,
        fontWeight: "bold",
        userSelect: "none",
        fontVariant: "small-caps",
        marginBottom: 8,
      },
    },
  },
  {
    col: {
      xs: 6,
    },
    label: "First Name",
    props: {
      required: true,
    },
    attribute: "firstName",
    component: "text-field",
    validations: {
      label: "First Name",
      required: "Required",
    },
    validationType: "string",
  },
  {
    col: {
      xs: 6,
    },
    label: "Last Name",
    props: {
      required: true,
    },
    attribute: "lastName",
    component: "text-field",
    validations: {
      label: "Last Name",
      required: "Required",
    },
    validationType: "string",
  },
  {
    label: "Address Line 1",
    props: {
      required: true,
    },
    attribute: "address1",
    component: "text-field",
    validations: {
      label: "Address 1",
      required: "Required",
    },
    validationType: "string",
  },
  {
    label: "Address Line 2",
    attribute: "address2",
    component: "text-field",
    validations: {
      label: "Address 2",
    },
    validationType: "string",
  },
  {
    col: {
      xs: 6,
    },
    label: "City",
    attribute: "city",
    component: "text-field",
    validations: {
      label: "City",
    },
    validationType: "string",
  },
  {
    col: {
      xs: 6,
    },
    label: "State",
    attribute: "state",
    component: "text-field",
    validations: {
      label: "State",
    },
    validationType: "string",
  },
  {
    col: {
      xs: 6,
    },
    label: "Postcode",
    props: {
      required: true,
    },
    attribute: "postcode",
    component: "text-field",
    validations: {
      label: "Postcode",
      required: "Required",
    },
    validationType: "string",
  },
  {
    col: {
      xs: 6,
    },
    label: "Country",
    props: {
      required: true,
    },
    attribute: "country",
    component: "text-field",
    validations: {
      label: "Country",
      required: "Required",
    },
    validationType: "string",
  },
  {
    options: [
      {
        label: "Same as Billing",
        value: true,
      },
    ],
    optionConfig: {
      key: "label",
      label: "label",
      value: "value",
    },
    attribute: "sameAsBilling",
    component: "checkbox-group",
  },
];
```

### Standard Code For All Forms

```jsx
import React, { useRef, useState } from "react";
import { FormBuilder } from "@jeremyling/react-material-ui-form-builder";
import { Button } from "@mui/material";
import { get, isEmpty } from "lodash";

async function validate(refs, form) {
  var errors = {};
  for (const [attribute, ref] of Object.entries(refs.current)) {
    if (ref.validate) {
      const error = await ref.validate(get(form, attribute));
      if (error.length) {
        errors[attribute] = error;
      }
    }
  }
  if (!isEmpty(errors)) {
    console.log(errors);
    return false;
  }
  return true;
}

export default function ShippingAddress() {
  const [form, setForm] = useState({});

  const refs = useRef({});

  const updateForm = (updates) => {
    const copy = { ...form };
    for (const [key, value] of Object.entries(updates)) {
      copy[key] = value;
    }
    setForm(copy);
  };

  const handleNext = async (event) => {
    event.preventDefault();
    const ok = await validate(refs, form);
    if (!ok) {
      return;
    }
    console.log(form);
  };

  return (
    <>
      <FormBuilder
        fields={fields}
        form={form}
        updateForm={updateForm}
        refs={refs}
      />
      <Button
        type="submit"
        variant="contained"
        color="primary"
        sx={{ mt: 1 }}
        onClick={handleNext}
      >
        Next
      </Button>
    </>
  );
}
```

### Explanation

Here, we only use a few components from the library, `display-text`, `text-field` and `checkbox-group`. There are many more you can use. You can view the [documentation here](https://github.com/jzcling/react-material-ui-form-builder#field-props). Just note, in general, all input components would have these core field props.

```javascript
{
  ...
  title: "State", // Display text above the input
  label: "State", // Input label
  attribute: "state", // Attribute of the form to set/control
  component: "text-field", // Type of component
  col: {
    // Breakpoints and corresponding width (1 - 12)
    xs: 6,
    sm: 6,
    ...
  },
  validations: {
    // Any validations accepted by yup
    required: true,
    length: 10,
    min: 5,
    max: 20,
    matches: ['/[a-z]/i', 'Can only contain letters'],
    email: true,
    url: true,
    uuid: true,
  },
}
```

### Concluding Remarks

I actually created the form above with 3 clicks and a few renames on [FormBlob](https://build.formblob.com/builder). Try it out and make building forms a breeze. If you're a developer, [contact us](https://build.formblob.com/p/05f25c5d-0f7b-446b-931c-afd2d876499b) and I'll turn on a feature for you to export the forms you build into your own project. Alternatively, you can even build and deploy a hosted form entirely on FormBlob and embed the form in your own domain. If you use this method, you don’t have to use React in your app and it still works out of the box!

If data privacy is a concern, you can define webhooks to call on each submission to pass the form data to your own backend. If you choose, FormBlob does not store any data on our servers beyond the form structure.