0

I am required to build a Form Builder component that builds forms based on the data that's coming from the backend in json format.. The Json is given below after description.. The Form component can have any number and type of fields.. As a first step I narrowed down the number of field types i might encounter. So I figured that in my case I will be dealing with 7 types of inputs->

  1. email
  2. password
  3. select
  4. file
  5. date
  6. input of type text
  7. input of type number

So .. Now i can create form elements on the basis of whatever fieldType attribute's value i am receiving from json... Everything is good, if there will only be one field of a particular field type. Because i can declare 7 state variables one for each formField type to store their values and perform form validations.

    const [textInputData, setTextInputData] = useState("")
    const [numberInputData, setNumberInputData] = useState("")
    const [emailInputData, setEmailInputData] = useState("")
    const [passwordInputData, setPasswordInputData] = useState("")
    const [dateInputData, setDateInputData] = useState("")
    const [fileInputData, setFileInputData] = useState("")
    const [selectInputData, setSelectInputData] = useState("")

But the problem is there can be any number of a particular field type in my form- for eg: there can be more than one email type field in my form say one for customer email id and the other for vendor email id.

The Question is -> Is there any way(or is it possible) that i can know how many form fields will come from backend and based on the number of formFields and their labels(see json) create state ... Or I should use a different approach..

This is the Component I am working on->

import {useState } from "react"
import {useDispatch, useSelector} from 'react-redux'
import {TextField, Autocomplete, Box, InputAdornment, FormControl, MenuItem, Button} from '@mui/material'
import DatePicker from "components/common/controls/DatePicker"
import { CloudUpload } from "@material-ui/icons"
import { makeStyles } from "@mui/styles"
import Select from "components/common/controls/Select"
import { useForm, Controller } from "react-hook-form"
import {yupResolver} from '@hookform/resolvers/yup'
import * as yup from 'yup'

const useStyles = makeStyles({
    textField: {

        '& .MuiInputBase-input': {
            opacity: '0'
        }
    }
})
const formData = [] 
// json data provided below

export const FormGenerator = () => {
    const leadData = useSelector(state => state.lead)

    const [textInputData, setTextInputData] = useState("")
    const [numberInputData, setNumberInputData] = useState("")
    const [emailInputData, setEmailInputData] = useState("")
    const [passwordInputData, setPasswordInputData] = useState("")
    const [dateInputData, setDateInputData] = useState("")
    const [fileInputData, setFileInputData] = useState("")
    const [selectInputData, setSelectInputData] = useState("")

    const dispatch = useDispatch()

    const classes = useStyles()

    const schema = yup.object().shape({
        email: yup.string().required('Email is required').email('Email is Invalid'),
        password: yup.string().min(6).max(20).required(),
        textInput: yup.string().required(),
        numberInput: yup.number().required().positive().integer(),
        date: yup.date().required()
    })

    const {register, handleSubmit, control, formState: {errors}} = useForm({
        resolver: yupResolver(schema)
    })


// check fieldType and render component accordingly
    const createFormElement = (el, index) => {
        console.log(el)
        switch(el.fieldType) {
            case "textInput":
               return createInputElement(el, index, 'textInput')
            case "numberInput":
                return createInputElement(el, index, 'numberInput')
            case "email":
                return createInputElement(el, index, "email")
            case "select":
                return createSelectElement(el, index)
            case "date":
                return createDateElement(el, index)
            case "upload":
                return createUploadElement(el, index)
        }
    }


    const handleInputChange = () => {

    }

    const createInputElement = (data, index, type) => {
        if(type && type==="email") {
            return (
            <Controller
                name={data.fieldType}
                control={control}
                defaultValue=""
                render={(props) => <TextField
                    {...props}
                    {...register}
                    onChange={handleInputChange}
                    variant="standard" 
                    fullWidth
                    error={!!errors.email}
                    helperText={errors.email? errors.email?.message: ''}
                    key={index}
                    label={data.label} 
                    color="secondary" 
                    sx={{marginBottom: '1rem'}}
                    />}
            />
            )
        } else if (type && type === "password") {
            return (
                <Controller
                    name={data.fieldType}
                    control={control}
                    defaultValue=""
                    render={(props) => <TextField
                        {...props}
                        {...register}
                        variant="standard"
                        fullWidth
                        type="password"
                        error={!!errors.password}
                        helperText={errors.password? errors.password?.message: ''}
                        key={index}
                        label={data.label}
                        color="secondary"
                        sx={{marginBottom: '1rem'}}
                    />}
                />
            )
        } else if (type && type === "textInput") {
            return (
                <Controller
                    name={data.fieldType}
                    control={control}
                    defaultValue=""
                    render={(props) => <TextField
                        {...props}
                        {...register}
                        variant="standard"
                        fullWidth
                        error={!!errors.textInput}
                        helperText={errors.textInput? errors.textInput?.message: ''}
                        key={index}
                        label={data.label}
                        color="secondary"
                        sx={{marginBottom: '1rem'}}
                    />}
                />
            )
        } else if (type && type === "numberInput") {
            return (
                <Controller
                    name={data.fieldType}
                    control={control}
                    defaultValue=""
                    render={(props) => <TextField
                        {...props}
                        {...register}
                        variant="standard"
                        fullWidth
                        error={!!errors.numberInput}
                        helperText={errors.numberInput? errors.numberInput?.message: ''}
                        key={index}
                        label={data.label}
                        color="secondary"
                        sx={{marginBottom: '1rem'}}
                    />}
                />
            )
        }
        
    }

    const createSelectElement = (data, index) => {
        return (
        <Select
          id="demo-simple-select-standard"
          value={""}
          onChange={handleChange}
          label={data.label}
          sx={{marginBottom: '1rem'}}
          options={data.options}
        />
        )
    }

    const createDateElement = (data,index) => {
        return (
        <Box 
        key={index}
        display={'flex'} 
        justifyContent={'start'} 
        marginBottom={2}>
            <DatePicker
            label={data.label}
            value={new Date()}
            onChange={(e) => console.log(e)}/>
        </Box>)
    }

    const createUploadElement = (data, index) => {
        return (
            <Box  
                key={index}
                display={'flex'}
                justifyContent={'start'}
                marginBottom={'1rem'}>
                    <TextField
                        variant="standard"
                        className={classes.textField}
                        type="file"
                        accept="image/*"
                        label={data.label}
                        InputProps={{
                            endAdornment: (
                            <InputAdornment position="start">
                                <CloudUpload />
                            </InputAdornment>
                            ),
                            register
                
                        }}
                    // onChange={onChange}
                />
            </Box>
        )
    }

    const handleChange = () => {
        console.log('')
    }

    const handleFormSubmission =  (data) => {
        console.log(data)
    }

    return (
        <form onSubmit={handleSubmit(handleFormSubmission)}>
            {formData.map((el, index) => (
                createFormElement(el, index)
            ))}
            <Button type="submit" variant="contained">Submit</Button>
        </form>
    )
}

[

    {
        "label": "Full Name",
        "required": "true",
        "fieldType": "textInput",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"fullName": ""}
    },
    {
        "label": "Mobile Number",
        "required": "true",
        "fieldType": "numberInput",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"mobileNumber": ""}
    },
    {
        "label": "Email",
        "required": "true",
        "fieldType": "email",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"email": ""}
    },
    {
        "label": "Product",
        "required": "true",
        "fieldType": "select",
        "options": [{
          "carLoan": "Car Loan",
          "homeLoan": "Home Loan",
          "goldLoan": "Gold Loan"
        }],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"selectedValue": ""}
    },
    {
        "label": "Product Schema",
        "required": "true",
        "fieldType": "select",
        "options": [{
          "test1": "Test 1",
          "test2": "Test 2",
          "test3": "Test 3"
        }],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"selectedValue": ""}
    },
    {
        "label": "Loan Amount",
        "required": "true",
        "fieldType": "numberInput",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"loanAmount": ""}
    },
    {
        "label": "Upload Adhaar",
        "required": "true",
        "fieldType": "upload",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"uploadAdhaar": ""}
    },
    {
        "label": "Upload Pan",
        "required": "true",
        "fieldType": "upload",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"uploadPan": ""}
    },
    {
        "label": "Callback Appointment",
        "required": "true",
        "fieldType": "date",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"callbackAppointment": ""}
    },
    {
        "label": "Branch Name",
        "required": "true",
        "fieldType": "textInput",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"callbackAppointment": ""}
    },
    {
        "label": "Created By",
        "required": "true",
        "fieldType": "textInput",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"callbackAppointment": ""}
    },
    {
        "label": "Assigned Person",
        "required": "true",
        "fieldType": "select",
        "options": [{
          "test1": "Test 1",
          "test2": "Test 2",
          "test3": "Test 3"
        }],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"assignedPerson": ""}
    },
    {
        "label": "Description",
        "required": "true",
        "fieldType": "textInput",
        "options": [],
        "textReturn": "true",
        "hasIcon": "false",
        "fieldKey": {"description": ""}
    }
  
]
  • You can use arrays to store an arbitrary number of emails, from which you render an input for each. But if you already know in advance the possibilities, e.g. customer email and vendor email, you'd be better off creating a separate one for each and render the input only if it exists in the data gotten from your backend. – rcshon Apr 15 '22 at 11:20
  • you can try getting the values with ref instead of state after you create all types of inputs [https://stackoverflow.com/questions/61245376/react-useref-with-array-for-dynamic](https://stackoverflow.com/questions/61245376/react-useref-with-array-for-dynamic) – codmitu Apr 16 '22 at 17:26

1 Answers1

0

You can use something like

const [state, setState] = useState({});
setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

This is used for updating multiple subvalues. This you can use if you have no idea how many fields you want from backend, and if you add any field in backend, then you dont have to create new state for that, this small code will work.

Check if this helps you.