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

React组件移除最后一题时selectedQuestion状态不更新问题

问题:删除最后一题后Question组件消失,State更新失效

我通过selectedQuestion状态渲染Question组件,但点击最后一题的关闭按钮执行removeQuestion后,状态未正确更新,指向旧索引导致组件消失。用Ref更新当前索引能正常工作,但用State就不行,推测是同一渲染周期内State未完成更新。已经尝试:

  • 将setState传入removeFunction中更新
  • 使用setState的更新器函数格式

核心原因

  1. State更新的异步性:在setSelectedQuestion的更新器里调用removeQuestion,两个State更新(组件内的selectedQuestion和Context的questions)都是异步的,且执行顺序不可控。当removeQuestion先完成时,questions数组长度减少,但此时selectedQuestion还没更新,导致后续渲染时selectedQuestion超出新数组的索引范围,找不到对应Question。
  2. 依赖旧的索引值:删除操作使用的index是当前渲染周期的旧值,当questions数组变化后,这个索引已经失效,而State更新后的新值还没同步到渲染上下文。

解决方案

核心思路是让selectedQuestion的更新依赖questions数组的最新状态,而不是在同一个回调里同步修改两个独立的State。具体步骤:

  1. 移除回调里同步调用removeQuestion和修改State的逻辑,先执行删除操作。
  2. 用useEffect监听questions数组的变化,自动调整selectedQuestion的值,确保它始终在有效索引范围内。
  3. 彻底移除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 21:56:00