如何编辑此 TextField?

How can I edit this TextField?

我有一个 Material UI 文本字段,它由从 API.

中拉出的嵌套 JSON 对象填充

数据可以显示在文本字段、日期选择器或 Select 框中。这是由FieldType决定的。

数据在文本字段、日期选择器和 Select 框中显示得很好,但无法更改。尝试更改任何输入中的文本会导致此错误消息:Uncaught TypeError: prev.fields is not iterable.

以下是我在各自输入中显示数据的方法。

{details["groups"]?.map((group) => {
    return (
        <Accordion>
            <AccordionSummary expandIcon={<ExpandMoreIcon />}>
                <Typography>{group?.GroupName}</Typography>
            </AccordionSummary>
            <AccordionDetails>
                <Box>
                    {group["fields"]?.map((row, index) => {
                        if (
                            row?.FieldType === "Text" ||
                            row?.FieldType === "Decimal" ||
                            row?.FieldType === "Number"
                        ) {
                            return (
                                <TextField
                                    value={row?.Value || ""}
                                    onChange={(e) => {
                                        setDetails((prev) => {
                                            const update = [...prev.fields];
                                            update[index] = {
                                                ...update[index],
                                                Value: e.target.value,
                                            };
                                            return { ...prev, fields: update
                                        });
                                    }}
                                    margin="normal"
                                    label={row["FieldName"]}
                                    />
                            );
                        }
                        if (row?.FieldType === "Date") {
                            return (
                                <TextField
                                    type="date"
                                    value={row?.Value || null}
                                    onChange={(e) => {
                                        setDetails((prev) => {
                                            const update = [...prev.fields];
                                            update[index] = {
                                                ...update[index],
                                                Value: e.target.value,
                                            };
                                            return { ...prev, fields: update 
                                        });
                                    }}
                                    label={row["FieldName"]}
                                    InputLabelProps={{
                                        shrink: true,
                                    }}
                                />
                            );
                        } else {
                            return (
                                <TextField
                                    value={row?.Value || ""}
                                    onChange={(e) => {
                                        setDetails((prev) => {
                                            const update = [...prev.fields];
                                            update[index] = {
                                                ...update[index],
                                                Value: e.target.value,
                                            };
                                            return { ...prev, fields: update 
                                        });
                                    }}
                                    select
                                    label={row?.FieldName}
                                >
                                    {row?.Choices.map((choice) => (
                                        <MenuItem key={choice} value= {choice}>
                                            {choice}
                                        </MenuItem>
                                    ))}
                                </TextField>
                            );
                        }
                    })}
                </Box>
            </AccordionDetails>
        </Accordion>
    );
})}

我的例子 JSON:

{
  "groups": [
    {
      "GroupName": "Details",
      "GroupOrder": 1,
      "fields": [
        "FieldId": 2,
        "FieldName": "Day",
        "FieldType": "Select",
        "Value": "Option1",
        "Choices": [
          "Option1",
          "Option2"
        ]
      ]
    },
    {
      "GroupName": "Attributes",
      "GroupOrder": 2,
      "fields": [
        {
          "FieldId": 2,
          "FieldName": "Night",
          "FieldType": "Text",
          "Value": "Night time",
          "Choices": [
            null
          ]
        },
        {
          "FieldId": 3,
          "FieldName": "Todays Date",
          "FieldType": "Date",
          "Value": "2020-08-12",
          "Choices": [
            null
          ]
        }
      ],
    }
  ]
}

API 通话:

const params = useParams();
    const [details, setDetails] = useState('');

    const fetchDetails = async () => {
        setDetails(
            await fetch(`/fiscalyears/FY2023/intakes/${params.id}/details`).then(
                (response) => response.json()
            )
        );
    };

useEffect(() => {
  fetchDetails();
}, []);

是否可以使用一种方法为多个嵌套 JSON 对象创建 textField 而不是对它们进行硬编码?

您只需要在 onChange 函数中做一些小改动,并在 map 函数上传递 'index' 参数

{questions["Days"]?.map((row, index) => (
        <TextField
          fullWidth
          multiline
          className="text-field"
          value={row?.Response || ""}
          onChange={(e) => {
            setQuestions((prev) => {
              const days = [...prev.Days];
              days[index] = {
                ...days[index],
                Response: e.target.value
              };
              return { ...prev, Days: days };
            });
          }}
          variant="outlined"
          margin="normal"
          label={row["Question"]}
        />
      ))}

用你的函数控制更新状态,然后用我提供的函数控制它,你会看到区别。

最终重构了您的代码。很多代码可以写的更好

Uncaught TypeError: prev.fields is not iterable.是因为你在handleChange.

中没有传递groupIndex

工作解决方案已放到 Github repo

看一下主要代码:

// noinspection JSIgnoredPromiseFromCall

import './App.css';
import {Accordion, AccordionDetails, AccordionSummary, Box, MenuItem, TextField, Typography} from "@mui/material";
import {useEffect, useState} from "react";
import YourJSON from "./YourJSON";

const App = () => {
    const [details, setDetails] = useState({});

    const fetchDetails = async () => {
        // imitate an api call
        setDetails(await YourJSON());
    };

    const generalFieldTypes = (fieldType) => {
        return ["Text", "Decimal", "Number"].includes(fieldType);
    }
    const dateFieldTypes = (fieldType) => {
        return fieldType === 'Date';
    }

    const handleChange = (e, fieldIndex, groupIndex) => {
        console.log(JSON.stringify(details));

        let update = details;
        update['groups'][groupIndex]['fields'][fieldIndex]['Value'] = e.target.value;

        console.log(JSON.stringify(update));

        setDetails(update);
    }

    const DynamicField = (field, fieldIndex, groupIndex) => {
        const {FieldId, FieldName, FieldType, Value, Choices} = field;

        if (generalFieldTypes(FieldType)) {
            return <TextField
                defaultValue={Value}
                onChange={
                    (e) => handleChange(e, fieldIndex, groupIndex)
                }
                label={FieldName}
                InputLabelProps={{shrink: true}}
            />;
        } else if (dateFieldTypes(FieldType)) {
            return <TextField
                type="date"
                defaultValue={Value}
                onChange={
                    (e) => handleChange(e, fieldIndex, groupIndex)
                }
                label={FieldName}
                InputLabelProps={{shrink: true}}
            />;
        } else {
            return <TextField
                defaultValue={Value}
                onChange={
                    (e) => handleChange(e, fieldIndex, groupIndex)
                }
                select
                label={FieldName}
            >
                {
                    Choices.map((choice) => (
                        <MenuItem key={choice} value={choice}>
                            {choice}
                        </MenuItem>
                    ))
                }
            </TextField>;
        }
    }

    useEffect(() => {
        fetchDetails();
    }, []);

    return (
        <div className="App">
            {details && details["groups"]?.map((group, groupIndex) => {
                    const {GroupName, GroupOrder, fields} = group;
                    return (
                        <Accordion>
                            {/*I won't have your expandIcon, thereby remove it*/}
                            <AccordionSummary>
                                <Typography>{GroupName}</Typography>
                            </AccordionSummary>
                            <AccordionDetails>
                                <Box>
                                    {
                                        fields.map((field, fieldIndex) => {
                                            return DynamicField(field, fieldIndex, groupIndex);
                                        })
                                    }
                                </Box>
                            </AccordionDetails>
                        </Accordion>
                    );
                }
            )}
        </div>
    );
}

export default App;