React组件移除最后一题时selectedQuestion状态不更新问题
问题:删除最后一题后Question组件消失,State更新失效
我通过selectedQuestion状态渲染Question组件,但点击最后一题的关闭按钮执行removeQuestion后,状态未正确更新,指向旧索引导致组件消失。用Ref更新当前索引能正常工作,但用State就不行,推测是同一渲染周期内State未完成更新。已经尝试:
- 将setState传入removeFunction中更新
- 使用setState的更新器函数格式
核心原因
- State更新的异步性:在
setSelectedQuestion的更新器里调用removeQuestion,两个State更新(组件内的selectedQuestion和Context的questions)都是异步的,且执行顺序不可控。当removeQuestion先完成时,questions数组长度减少,但此时selectedQuestion还没更新,导致后续渲染时selectedQuestion超出新数组的索引范围,找不到对应Question。 - 依赖旧的索引值:删除操作使用的
index是当前渲染周期的旧值,当questions数组变化后,这个索引已经失效,而State更新后的新值还没同步到渲染上下文。
解决方案
核心思路是让selectedQuestion的更新依赖questions数组的最新状态,而不是在同一个回调里同步修改两个独立的State。具体步骤:
- 移除回调里同步调用
removeQuestion和修改State的逻辑,先执行删除操作。 - 用
useEffect监听questions数组的变化,自动调整selectedQuestion的值,确保它始终在有效索引范围内。 - 彻底移除Ref的依赖,统一用State管理选中状态,避免State和Ref不一致的问题。
修改后的CreateMcq组件代码
import React, { useContext, useEffect, useState } from "react"; import { MCQContext } from "../../../context/McqContext"; import styles from "./CreateMcq.module.css"; import Question from "./Question"; const CreateMcq = () => { const { questions, addQuestion, removeQuestion, updateTimerType } = useContext(MCQContext); const [selectedQuestion, setSelectedQuestion] = useState(0); const handleAddQuestion = () => { addQuestion({ id: questions.length, question: "", optionTypes: [ { name: "Text", value: "Text" }, { name: "Image URL", value: "Image URL" }, { name: "Text and Image URL", value: "Text and Image URL" }, ], optionsType: "Text", options: Array.from({ length: 4 }, () => ({ content: "", imageContent: "" })), timerType: "OFF", correctOption: 0, }); setSelectedQuestion(questions.length); }; const handleTimerTypeChange = (e) => { const newTimerType = e.target.value; updateTimerType(selectedQuestion, newTimerType); }; // 监听questions变化,自动调整选中索引 useEffect(() => { setSelectedQuestion(prev => { // 如果当前选中索引超出新数组长度,切换到最后一题 if (prev >= questions.length) { return Math.max(0, questions.length - 1); } return prev; }); }, [questions.length]); const handleRemoveQuestion = (index) => { if (index > 0) { removeQuestion(index); } }; return ( <div className={styles.container}> <div className={styles.questionBox}> <div className={styles.questionHeader}> <div className={styles.addQuestionBar}> {questions.map((q, index) => ( <div key={q.id} className={`${styles.questionCircle} ${index === selectedQuestion ? styles.questionCircleActive : ""}`} onClick={() => setSelectedQuestion(index)} > <p>{index + 1}</p> {index === questions.length - 1 && index > 0 && ( <span className={styles.questionRemoveIcon} onClick={(e) => { e.stopPropagation(); // 防止触发外层的选中事件 handleRemoveQuestion(index); }} > x </span> )} </div> ))} {questions.length < 5 && ( <div className={styles.addQuestionCircle} onClick={handleAddQuestion}> + </div> )} </div> <div className="totalQuestions">Max 5 Questions.</div> </div> {questions.map((q, index) => ( index === selectedQuestion && ( <Question key={q.id} index={q.id} question={q.question} options={q.options} timerType={q.timerType} optionTypes={q.optionTypes} selectedOptionType={q.optionsType} /> ) ))} </div> <div className={styles.timerType}> <div style={{ marginRight: "auto" }}>Timer Type:</div> <label> <input type="radio" name="timerType" value="5 Sec" onChange={handleTimerTypeChange} checked={questions[selectedQuestion]?.timerType === "5 Sec"} />{" "} 5 Sec </label> <label style={{ marginLeft: "0.5rem" }}> <input type="radio" name="timerType" value="10 Sec" onChange={handleTimerTypeChange} checked={questions[selectedQuestion]?.timerType === "10 Sec"} /> 10 Sec </label> <label style={{ marginLeft: "0.5rem" }}> <input type="radio" name="timerType" value="OFF" onChange={handleTimerTypeChange} checked={questions[selectedQuestion]?.timerType === "OFF"} />{" "} OFF </label> </div> <div className={styles.btnContainer}> <button className={styles.cancelBtn}>Cancel</button> <button className={styles.createBtn}>Create Quiz</button> </div> </div> ); }; export default CreateMcq;
关键修改点
- 移除了
selectedQuestionRef,统一用State管理选中状态 - 删除操作改为只调用
removeQuestion,通过useEffect自动调整选中索引 - 给删除按钮添加
e.stopPropagation(),防止误触发外层的选中事件 - 简化了Question组件的渲染逻辑,去掉多余的空Fragment
内容的提问来源于stack exchange,提问作者aditya rawat
相关产品推荐
相关产品推荐

