动态表单组件的状态维护



我需要构建一个表单生成器组件,该组件基于json格式的后端数据构建表单。Json在描述后给出如下。Form组件可以有任意数量和类型的字段。作为第一步我缩小了可能遇到的字段类型的数量。因此,我认为在我的例子中,我将处理7种类型的输入->

  1. 选择
  2. 文件
  3. text类型输入
  4. 数字类型输入

. .现在我可以根据我从json接收的任何fieldType属性的值创建表单元素…如果只有一个特定字段类型的字段,那么一切都很好。因为我可以为每个formField类型声明7个状态变量来存储它们的值并执行表单验证。

    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("")

但是问题是在我的表单中可以有任何数量的特定字段类型-例如:在我的表单中可以有多个电子邮件类型字段,例如一个用于客户电子邮件id,另一个用于供应商电子邮件id。

问题是->是否有任何方式(或它是可能的),我可以知道有多少表单字段将来自后端,并基于表单字段及其标签的数量(见json)创建状态…或者我应该使用不同的方法…

这是我正在处理的组件->

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

你可以使用

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

用于更新多个子值。如果你不知道后端需要多少字段,你可以使用这个,如果你在后端添加任何字段,那么你不必为它创建新的状态,这段小代码将工作。

检查这是否对你有帮助。

最新更新