Dynamic form in Reactjs

Viewed 674

I am generating the form based on the data from API.

Form Data: (Api returns array of object but in my example I have converted it to JSON for better understanding but data is array of object like below)

  [
    {
      "id": "formDescription",
      "label": "Form Description",
      "type": { "name": "text" },
      "value": {
        "value": "Use this request for vaccination status",
        "type": {
          "name": "string"
        }
      },
      "properties": { "viewOnly": "true" },
      "typeName": "text"
    },
    {
      "id": "vaccinated",
      "label": "Vaccinated",
      "type": {
        "values": {
          "vaccinated": "Vaccinated",
          "not_vaccinated": "Not Vaccinated"
        },
        "name": "enum"
      },
      "value": {
        "value": null,
        "type": {
          "name": "string"
        }
      },
      "validationConstraints": [],
      "properties": { "groupName": "Letter Details" },
      "typeName": "enum"
    },
    {
      "id": "dose",
      "label": "Dose",
      "type": {
        "values": {
          "dose_one": "Dose One",
          "dose_two": "Dose Two"
        },
        "name": "enum"
      },
      "value": {
        "value": null,
        "type": {
          "name": "string"
        }
      },
      "validationConstraints": [],
      "properties": { "groupName": "Letter Details" },
      "typeName": "enum"
    },
    {
      "id": "reason",
      "label": "Reason",
      "type": { "name": "string" },
      "value": {
        "value": null,
        "type": {
          "name": "string"
        }
      },
      "validationConstraints": [],
      "properties": { "groupName": "Letter Details", "maxlength": "50" },
      "typeName": "string"
    },
    {
      "id": "doseOneDetails",
      "label": "Dose One Details",
      "type": { "name": "textArea" },
      "defaultValue": null,
      "value": {
        "value": null,
        "type": {
          "name": "string"
        }
      },
      "validationConstraints": [
        { "name": "maxlength", "config": "400", "validator": null }
      ],
      "properties": { "groupName": "Letter Details", "maxlength": "400" },
      "typeName": "textArea"
    },
    {
      "id": "doseTwoDetails",
      "label": "Dose Two Details",
      "type": { "name": "textArea" },
      "defaultValue": null,
      "value": {
        "value": null,
        "type": {
          "name": "string"
        }
      },
      "validationConstraints": [
        { "name": "maxlength", "config": "400", "validator": null }
      ],
      "properties": { "groupName": "Letter Details", "maxlength": "400" },
      "typeName": "textArea"
    }
  ]

So based on this data I need to generate a form. And generating of form is already working as per the below provided example.

Requirement:

Scenario 1:

User selects vaccinated as an option from first select box, then the select box with data as Dose One and Dose Two appears. Then if user select any of the dose then he need to add details on that respective dose.

Scenario 2: (Working Now)

User selects Not Vaccinated as an option from first select box, then the reason select box will be displayed.

Scenario 3: (Working Now)

User selects Other as an option from first select box, then the moreDetails text box will be displayed.

Technical Requirement:

To achieve this condition checks, I have included multiple if..else..if conditions like,

  if (Array.isArray(name) && name.length) {
    const [fieldName] = name;
    if (fieldName === "vaccination_status" && value === "vaccinated") {
      setFieldsToHide(["reason", "moreDetails"]);
    } else if (
      fieldName === "vaccination_status" &&
      value === "not_vaccinated"
    ) {
      setFieldsToHide([
        "dose",
        "doseOneDetails",
        "doseTwoDetails",
        "moreDetails"
      ]);
    } else if (fieldName === "vaccination_status" && value === "other") {
      setFieldsToHide([
        "reason",
        "dose",
        "doseOneDetails",
        "doseTwoDetails"
      ]);
    }
  }

So for example if my form grows and in the first select box if more options are added then after selecting an option from first select box, the second select/input box gets displayed. If second one is a select box then again based on that the next select/input box gets displayed and goes on.

In the above given example consider vaccination. Once user selects the vaccination, the dose select box gets displayed then after the dose the respective dose details input box gets displayed. (So one depends on another is the requirement).

Working Example:

Edit antd typescript (forked)

How can we remove this multiple if...else...if condition check with hard coded values and make it configurable may be by modifying the original data structure or some other way?

Kindly please fork the given codesandbox and provide solution which would me much more highly appreciated.

3 Answers

I think you just need to add a "hiddenField" key which contains all keys need to hide whenever the field value have changed.

[
 ...
 {
    "hiddenField": {
    "vaccinated": ["reason", "moreDetails"],
    "not_vaccinated": [
      "dose",
      "doseOneDetails",
      "doseTwoDetails",
      "moreDetails"
    ],
    "other": ["reason", "dose", "doseOneDetails", "doseTwoDetails"]
    }
 }
...
]

And then we change your entire if..else code to this

  if (Array.isArray(name) && name.length) {
    const [fieldName] = name;
    if (fieldName === "vaccination_status") {
      setFieldsToHide((data[1] as any).hiddenField[value]);
    }
  }

Here is an example

if you need an "own solution" there are too many ways to achieve it.

may be this little example is useful for you, but you must develop it further.

data:

"properties": { "groupName": "Letter Details", "maxlength": "400", "display": {"equals": ["vaccinated", "not_vaccinated", "none", "inherits"]} } 

and code:

if ( typeof Properties.display === "string" )
  setProperty("display",Properties.display);
else if ( typeof Properties.display === "object" )
  if( Properties.display.equals )
    setProperty("display", getFormValue(Properties.display.equals[0]) ===  Properties.display.equals[1] ? Properties.display.equals[2] : Properties.display.equals[3]
  else if( Properties.display.less_than )
     ...

Also, each property can be automatized : if ( typeof Properties.[counter] ) ...

in demo.tsx you can try (you can rename and change argument for better)

import React, { useState, useCallback, useEffect } from "react";
import { GeneratedForm } from "./GenerateForm/GenerateForm";
import { FieldData } from "rc-field-form/lib/interface";
const jsonData = require("./data.json");
const loadData = () => JSON.parse(JSON.stringify(jsonData));

const objHide = {
  vaccinated: ["reason", "moreDetails"],
  not_vaccinated: ["dose", "doseOneDetails", "doseTwoDetails", "moreDetails"],
  other: ["dose", "doseOneDetails", "doseTwoDetails", "reason"]
};

function Demo() {
  const [data, setData] = useState([]);

  const [fieldsToHide, setFieldsToHide] = useState<string[]>([
    "reason",
    "dose",
    "doseOneDetails",
    "doseTwoDetails",
    "moreDetails"
  ]);

  useEffect(() => {
    if (data.length === 0) {
      setData(loadData().data);
    }
  }, [data]);

  const onFieldsChange = useCallback(
    (changedFields: FieldData[], allFields: FieldData[]) => {
      const valueSelected = changedFields[0].value;
      const hideArray = objHide[valueSelected];
      setFieldsToHide(hideArray);
    },
    []
  );

  return (
    <GeneratedForm
      formData={data}
      onFieldsChange={onFieldsChange}
      fieldsToHide={fieldsToHide}
    />
  );
}

export default Demo;
Related