You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Material UI React步进器单选按钮值互相覆盖问题求助

解决Material UI Stepper中单选按钮跨步骤互相影响的问题

问题原因

  1. 所有RadioGroup使用了相同的name="radio-buttons-group",浏览器会将不同步骤的单选控件判定为同一组,导致选中状态互相覆盖。
  2. 未单独存储每个问题的选中值,切换步骤后无法保留之前的选择,进一步加剧状态混乱。

修复后的完整代码

import Box from "@mui/material/Box";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import StepLabel from "@mui/material/StepLabel";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import FormControl from "@mui/material/FormControl";
import FormLabel from "@mui/material/FormLabel";
import { useState, Fragment } from "react";

const questions = [
    {
      "id": "43c98ce8-a07a-4dc2-80f6-c1b2a2485f06",
      "text": "My favorite color is?",
      "num": 1,
      "choices": [
        {
          "text": "Red",
          "score": 1,
          "color": 1
        },
        {
          "text": "Blue",
          "score": 2,
          "color": 2
        },
        {
          "text": "Green",
          "score": 3,
          "color": 3
        },
        {
          "text": "Yellow",
          "score": 4,
          "color": 4
        },
        {
          "text": "Orange",
          "score": 5,
          "color": 5
        }
      ]
    },
    {
      "id": "d50a597f-632b-4f7b-89e6-6d85b50fd1c9",
      "text": "My favorite food is?",
      "num": 2,
      "choices": [
        {
          "text": "Pasta",
          "score": 5,
          "color": 1
        },
        {
          "text": "Pizza",
          "score": 4,
          "color": 2
        },
        {
          "text": "Burger",
          "score": 3,
          "color": 3
        },
      ]
    },
    {
        "id": "f6076eea-56ae-4b46-97f1-5f94a7676c96",
        "text": "My favorite mode of transport is?",
        "num": 3,
        "choices": [
          {
            "text": "Train",
            "score": 1,
            "color": 1
          },
          {
            "text": "Plane",
            "score": 2,
            "color": 2
          },
          {
            "text": "Car",
            "score": 3,
            "color": 3
          },
          {
            "text": "Boat",
            "score": 4,
            "color": 4
          },
        ]
    }
];

function Questions() {
    const [activeStep, setActiveStep] = useState(0);
    const [skipped, setSkipped] = useState(new Set());
    const [answers, setAnswers] = useState({});
    
    const handleChange = (questionId) => (event) => {
        setAnswers(prev => ({
            ...prev,
            [questionId]: event.target.value
        }));
      };
  
    const isStepOptional = (step) => {
      return step === 1;
    };
  
    const isStepSkipped = (step) => {
      return skipped.has(step);
    };
  
    const handleNext = () => {
      let newSkipped = skipped;
      if (isStepSkipped(activeStep)) {
        newSkipped = new Set(newSkipped.values());
        newSkipped.delete(activeStep);
      }
  
      setActiveStep((prevActiveStep) => prevActiveStep + 1);
      setSkipped(newSkipped);
    };
  
    const handleBack = () => {
      setActiveStep((prevActiveStep) => prevActiveStep - 1);
    };
  
    const handleSkip = () => {
      if (!isStepOptional(activeStep)) {
        throw new Error("You can't skip a step that isn't optional.");
      }
  
      setActiveStep((prevActiveStep) => prevActiveStep + 1);
      setSkipped((prevSkipped) => {
        const newSkipped = new Set(prevSkipped.values());
        newSkipped.add(activeStep);
        return newSkipped;
      });
    };
  
    const handleReset = () => {
      setActiveStep(0);
      setAnswers({});
    };

    const currentQuestion = questions[activeStep];

  return (
    <Box sx={{ width: "100%", borderRadius: "5px",
    height: "315px",
    padding: "25px",
    overflow: "hidden",
    position: "absolute",
    backgroundColor: "white",
    width: "70%",
    zIndex: "10",
    top: "0",
    left: "0",
    right: "0",
    bottom: "0",
    margin: "auto" }}>
      <Stepper activeStep={activeStep}>
        {questions.map((question, index) => {
          const stepProps = {};
          const labelProps = {};
          if (isStepSkipped(index)) {
            stepProps.completed = false;
          }
          return (
            <Step key={question.id} {...stepProps}>
              <StepLabel {...labelProps}>{question.text}</StepLabel>
            </Step>
          );
        })}
      </Stepper>

      <FormControl sx={{ margin: "0 auto" }}>
        <RadioGroup
          aria-labelledby={`radio-group-${currentQuestion.id}`}
          name={`radio-group-${currentQuestion.id}`}
          value={answers[currentQuestion.id] || ""}
        >
        {currentQuestion.choices.map((choice, index) => 
        {
            return (
                <FormControlLabel
                key={`${currentQuestion.id}-${index}`}
                control={
                  <Radio
                    inputProps={{
                      "aria-label": choice.text
                    }}
                  />
                }
                value={choice.score.toString()}
                label={choice.text}
                onChange={handleChange(currentQuestion.id)}
              />
            )
        })}
        </RadioGroup>
      </FormControl>
      {activeStep === questions.length ? (
        <Fragment>
          <Typography >
            All steps completed - you're finished
          </Typography>
          <Box sx={{ display: "flex", flexDirection: "row", pt: 2, backgroundColor: "white" }}>
            <Box sx={{ flex: "1 1 auto" }} />
            <Button onClick={handleReset}>Reset</Button>
          </Box>
        </Fragment>
      ) : (
        <Fragment>
          <Typography sx={{ mt: 2, mb: 1 }}>Step {activeStep + 1}</Typography>
          <Box sx={{ display: "flex", flexDirection: "row", pt: 2, backgroundColor: "white" }}>
            <Button
              color="inherit"
              disabled={activeStep === 0}
              onClick={handleBack}
              sx={{ mr: 1 }}
            >
              Back
            </Button>
            <Box sx={{ flex: "1 1 auto", backgroundColor: "white" }} />
            {isStepOptional(activeStep) && (
              <Button color="inherit" onClick={handleSkip} sx={{ mr: 1 }}>
                Skip
              </Button>
            )}

            <Button onClick={handleNext}>
              {activeStep === questions.length - 1 ? "Finish" : "Next"}
            </Button>
          </Box>
        </Fragment>
      )}
    </Box>
  )
}

export default Questions;

关键修改点

  1. 为每个RadioGroup设置唯一name,使用问题的id拼接,避免浏览器将不同步骤的单选控件视为同一组。
  2. 新增answers状态对象,存储每个问题的选中答案,保证不同步骤的选择独立存储。
  3. 为RadioGroup绑定value属性,切换步骤时自动显示该问题之前的选中状态。
  4. 修改handleChange为柯里化函数,接收当前问题的id,确保选中值对应存储到正确的问题条目下。
  5. 修正了代码中backgroudColor的拼写错误,并为列表项添加了更可靠的key。

内容的提问来源于stack exchange,提问作者CHEESE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 10:27:05