Material UI React步进器单选按钮值互相覆盖问题求助
解决Material UI Stepper中单选按钮跨步骤互相影响的问题
问题原因
- 所有
RadioGroup使用了相同的name="radio-buttons-group",浏览器会将不同步骤的单选控件判定为同一组,导致选中状态互相覆盖。 - 未单独存储每个问题的选中值,切换步骤后无法保留之前的选择,进一步加剧状态混乱。
修复后的完整代码
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;
关键修改点
- 为每个
RadioGroup设置唯一name,使用问题的id拼接,避免浏览器将不同步骤的单选控件视为同一组。 - 新增
answers状态对象,存储每个问题的选中答案,保证不同步骤的选择独立存储。 - 为
RadioGroup绑定value属性,切换步骤时自动显示该问题之前的选中状态。 - 修改
handleChange为柯里化函数,接收当前问题的id,确保选中值对应存储到正确的问题条目下。 - 修正了代码中
backgroudColor的拼写错误,并为列表项添加了更可靠的key。
内容的提问来源于stack exchange,提问作者CHEESE
相关产品推荐
相关产品推荐

