无法在 Material UI 中动态设置 Select 框的初始状态

Unable to dynamically set the initial state of a Select box in Material UI

我正在使用 Material UI 5.7.0。我创建了一个调查表,用户可以填写该表并单击按钮下载包含他们的答案的 json 文件。我想在名为 questionsObject 的 json 对象中管理我的问题。使用同一个对象(或从它动态创建的对象)来加载表单的初始状态以及更新它似乎是有意义的。

下面的代码有效,但我已经在 defaultValues 对象中静态定义了我的默认值。每次我向问题对象添加新问题时,我都需要更新它。我真的希望它从 questionsObject 的值字段中读取默认值。

工作代码

    import React, {useEffect, useState} from "react";
    import Grid from "@mui/material/Grid";
    import FormControlLabel from "@mui/material/FormControlLabel";
    import FormControl from "@mui/material/FormControl";
    import FormLabel from "@mui/material/FormLabel";
    import RadioGroup from "@mui/material/RadioGroup";
    import Radio from "@mui/material/Radio";
    import Button from "@mui/material/Button";
    import Select from '@mui/material/Select';
    import { saveAs } from 'file-saver';
    import {InputLabel, MenuItem} from "@mui/material";

    const questionsObject = {
        q1: {
            questionText: "Animals",
            type: "BOTH",
            displayType: "select",
            menuItems: [
                '',
                "Dogs",
                "Cats",
                "Elephants"
            ],
            value: ''
        },
        q2: {
            questionText: "Favorite Pizza Topping",
            type: "BOTH",
            displayType: "select",
            menuItems: [
                '',
                "Pepperoni",
                "Mushrooms"
            ],
            value: ''
        },
        q3: {
            questionText: "Question text",
            type: "BOTH",
            displayType: "radioGroup",
            value: "true"
        },
        q4: {
            questionText: "Question text",
            type: "BOTH",
            displayType: "radioGroup",
            value: "no answer"
        },
        q5: {
            questionText: "Question text",
            type: "BE",
            displayType: "radioGroup",
            value: "no answer"
        }
    }

    const defaultValues = {
        q1: '',
        q2: '',
        q3: "no answer",
        q4: "no answer",
        q5: "no answer"
    }

    const MyForm = () => {
        const [formValues, setFormValues] = useState(defaultValues);

        // useEffect(() => {
        //     const outputObj = {}
        //     Object.keys(questionsObject).map((question) =>
        //         outputObj[question.id] = question.value
        //     );
        //     setFormValues(outputObj)
        // }, []);

        const handleInputChange = (e) => {
            const { name, value} = e.target;
            setFormValues({
                ...formValues,
                [name]: value
            });
        };

        const handleSubmit = (event) => {
            event.preventDefault();
            const outputObj = {}
            Object.keys(questionsObject).map((question) =>
                outputObj[question.id] = {
                    questionText: question.questionText,
                    type: question.type,
                    answer: formValues[question.id]
                }
            );

            console.log(outputObj);
            const outputObjJSON = JSON.stringify(outputObj, null, 2);
            const blob = new Blob([outputObjJSON], {type: "application/json"});
            saveAs(blob, "answerfile.json");
        };

        const getQuestionElement = (questionId) => {
            const question = questionsObject[questionId];
            switch(question.displayType) {
                case "radioGroup":
                    return (
                        <Grid item key={questionId}>
                            <FormControl>
                                <FormLabel>{question.questionText}</FormLabel>
                                <RadioGroup
                                    questiontype = {question.type}
                                    name={questionId}
                                    value={formValues[questionId]}
                                    onChange={handleInputChange}
                                    row
                                >
                                    <FormControlLabel
                                        value="no answer"
                                        control={<Radio size="small" />}
                                        label="No Answer"
                                    />
                                    <FormControlLabel
                                        value="false"
                                        control={<Radio size="small" />}
                                        label="False"
                                    />
                                    <FormControlLabel
                                        value="true"
                                        control={<Radio size="small" />}
                                        label="True"
                                    />
                                    <FormControlLabel
                                        value="NA"
                                        control={<Radio size="small" />}
                                        label="Not Applicable"
                                    />
                                </RadioGroup>
                            </FormControl>
                        </Grid>
                    );
                case "select":
                    return (
                        <Grid item key={questionId}>
                            <FormControl sx={{ m: 2, minWidth: 200 }}>
                                <InputLabel>{question.questionText}</InputLabel>
                                <Select
                                    name={questionId}
                                    value={formValues[questionId]}
                                    label={question.questionText}
                                    onChange={handleInputChange}
                                >
                                    {question.menuItems.map((item, index) =>
                                        <MenuItem key={index} value={item}>{item}</MenuItem>
                                    )}
                                </Select>
                            </FormControl>
                        </Grid>
                    );
                default:
            }
        }

        return formValues ? (
            <form onSubmit={handleSubmit}>
                <Grid
                    container
                    spacing={0}
                    direction="column"
                    alignItems="left"
                    justifyContent="center">
                    {Object.keys(questionsObject).map((questionId =>
                            getQuestionElement(questionId)
                    ))}
                    <Button variant="contained" color="primary" type="submit">
                        Download
                    </Button>
                </Grid>
            </form>
        ): null  ;
    };
    export default MyForm;

我尝试过的事情

我试过像这样从没有默认状态开始:

const [formValues, setFormValues] = useState();

并取消注释 useEffect() 但我得到这个错误:

MUI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are ``, `Dogs`, `Cats`, `Elephants`. 

此外,我的单选按钮没有设置默认值。

这导致我尝试在 RadioGroup 和 Select 组件上使用 defaultValue 字段。

正在尝试像这样在默认值 属性 中设置初始值:

defaultValue=""

defaultValue={question.value}

给我这个错误:

MUI: A component is changing the uncontrolled value state of Select to be controlled.
Elements should not switch from uncontrolled to controlled (or vice versa).
Decide between using a controlled or uncontrolled Select element for the lifetime of the component.
The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.

如何删除 defaultValues 对象并使用 questionsObject 中的值字段代替初始状态?

问题出在你的逻辑中 useEffect:

useEffect(() => {
   const outputObj = {}
   
   Object.keys(questionsObject).map(
      (question) => outputObj[question.id] = question.value
   );
   console.log(outputObj);
   setFormValues(outputObj)
}, []);

/// OUTPUT of outputObj:
{undefined: undefined}

您应该将其更改为:

useEffect(() => {
    const outputObj = {};
    Object.keys(questionsObject).map(
      (question) => (outputObj[question] = questionsObject[question].value)
    );
    console.log(outputObj);
    setFormValues(outputObj);
  }, []);

/// OUTPUT of outputObj:
{q1: "", q2: "", q3: "true", q4: "no answer", q5: "no answer"}

此外,避免使用 switch/case - 主要是为了不对 default 值做任何事情。在这种情况下,更喜欢使用 && 运算符 (docs) 的条件渲染:

const getQuestionElement = (questionId) => {
    const question = questionsObject[questionId];
    return (
      <React.Fragment key={questionId}>
        {question.displayType === "radioGroup" && (
          <Grid item>
            <FormControl>
              <FormLabel>{question.questionText}</FormLabel>

              <RadioGroup
                questiontype={question.type}
                name={questionId}
                value={formValues[questionId]}
                onChange={handleInputChange}
                row
              >
                <FormControlLabel
                  value="no answer"
                  control={<Radio size="small" />}
                  label="No Answer"
                />
                <FormControlLabel
                  value="false"
                  control={<Radio size="small" />}
                  label="False"
                />
                <FormControlLabel
                  value="true"
                  control={<Radio size="small" />}
                  label="True"
                />
                <FormControlLabel
                  value="NA"
                  control={<Radio size="small" />}
                  label="Not Applicable"
                />
              </RadioGroup>
            </FormControl>
          </Grid>
        )}
        {question.displayType === "select" && (
          <Grid item>
            <FormControl sx={{ m: 2, minWidth: 200 }}>
              <InputLabel>{question.questionText}</InputLabel>
              <Select
                name={questionId}
                value={formValues[questionId]}
                label={question.questionText}
                onChange={handleInputChange}
              >
                {question.menuItems.map((item, index) => (
                  <MenuItem key={index} value={item}>
                    {item}
                  </MenuItem>
                ))}
              </Select>
            </FormControl>
          </Grid>
        )}
      </React.Fragment>
    );
  };