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

React Quiz应用checkAnswers函数异常:始终返回首个答案而非用户选择项

问题分析:选择任意答案始终只显示第一个答案的原因及修复

现象

无论点击哪个选项,界面上始终只有第一个答案呈现选中状态,同时点击「Check Answers」后也无法正确统计得分。

问题根源

1. 单选逻辑缺失

holdAnswer函数仅切换当前点击选项的isChosen状态,但没有取消同一问题下其他选项的选中标记。这会导致多个选项同时被标记为选中,而界面渲染时可能因为样式判断逻辑,只呈现第一个选中项的状态。

2. 得分统计逻辑完全错误

原checkAnswers里的查找代码answer.answer.id === answer.answer.isChosen完全不符合逻辑:

  • answer.answer是字符串类型的答案文本,根本不存在id和isChosen属性
  • 正确逻辑应该是筛选出isChosen为true的选项

3. 列表渲染的Key值错误

渲染问题列表时,外层<section>使用index作为key,而非问题的唯一ID。当问题状态更新时,React可能会复用旧DOM节点,导致状态渲染异常。

修复方案

1. 完善单选逻辑

在holdAnswer中,更新问题答案时,将当前点击的选项设为选中,同时把其他选项的选中状态置为false,确保同一问题下只有一个选项被选中。

2. 修正得分统计逻辑

遍历问题时,正确筛选出isChosen为true的选项,再判断其是否为正确答案,最后统一更新分数(避免循环内多次调用setState)。

3. 使用唯一Key值

将问题列表外层<section>的key改为question.id,确保React能正确识别每个问题节点。

额外优化

添加答案顺序打乱逻辑,避免正确答案固定在最后一个位置。


修复后的完整代码

App.js

import { useState } from "react";
import QuestionSet from "./components/QuestionSet";
import Answers from "./components/Answers";
import { nanoid } from "nanoid";

function App() {
  const [isQuesLoaded, setIsQuesLoaded] = useState(false);
  const [questions, setQuestions] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [score, setScore] = useState(0);

  async function startQuiz() {
    try {
      setIsQuesLoaded(true);
      const response = await fetch(
        "https://opentdb.com/api.php?amount=5&category=12&difficulty=easy&type=multiple"
      );
      const data = await response.json();
      const allQuestions = data.results;
      const listOfQuestions = allQuestions.map((item) => {
        const allAnswers = [
          {
            id: nanoid(),
            isCorrect: false,
            isChosen: false,
            answer: item.incorrect_answers[0]
          },
          {
            id: nanoid(),
            isCorrect: false,
            isChosen: false,
            answer: item.incorrect_answers[1]
          },
          {
            id: nanoid(),
            isCorrect: false,
            isChosen: false,
            answer: item.incorrect_answers[2]
          },
          {
            id: nanoid(),
            isCorrect: true,
            isChosen: false,
            answer: item.correct_answer
          }
        ];
        // 打乱答案顺序,避免正确答案固定在最后
        allAnswers.sort(() => Math.random() - 0.5);
        return {
          id: nanoid(),
          question: item.question,
          answers: allAnswers
        };
      });
      setQuestions(listOfQuestions);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  function holdAnswer(questionId, answerId) {
    setQuestions((prevQuestion) =>
      prevQuestion.map((question) =>
        question.id === questionId
          ? {
              ...question,
              answers: question.answers.map((answer) =>
                answer.id === answerId
                  ? { ...answer, isChosen: true }
                  : { ...answer, isChosen: false } // 取消其他选项的选中状态
              )
            }
          : question
      )
    );
  }

  function checkAnswers() {
    let correctCount = 0;
    questions.forEach(question => {
      const selectedAnswer = question.answers.find(answer => answer.isChosen);
      if (selectedAnswer && selectedAnswer.isCorrect) {
        correctCount += 1;
      }
    });
    setScore(correctCount); // 统一更新分数,避免循环内多次setState
  }

  const questionElm = questions.map((question) => {
    return (
      <section key={question.id}> {/* 使用问题唯一ID作为key */}
        <QuestionSet question={question.question} />
        <Answers
          answers={question.answers}
          holdAnswer={(answerId) => holdAnswer(question.id, answerId)}
        />
      </section>
    );
  });

  return (
    <div className="App">
      {!isQuesLoaded ? (
        <main>
          <h1 className="title-app">Quizzical</h1>
          <p className="desc-app">Some description if needed</p>
          <button className="btn" onClick={startQuiz}>
            Start Quiz
          </button>
        </main>
      ) : (
        <main className="quest-box">
          {loading && <div>Loading data...</div>}
          {error && <div>{`There is a problem fetchning data = ${error}`}</div>}
          <section className="quest-content">{questionElm}</section>

          <button className="answer-btn" onClick={checkAnswers}>Check Answers</button>
        </main>
      )}
    </div>
  );
}

export default App;

Answers.js

function Answer(props) {
  const styles = {
    backgroundColor: props.answer.isChosen ? "#D6DBF5" : "transparent"
  };
  return (
    <div
      className="answer-div"
      style={styles}
      onClick={() => props.holdAnswer(props.answer.id)}
    >
      <p>{props.answer.answer}</p>
    </div>
  );
}

export default function Answers(props) {
  return (
    <section className="answer-container">
      {props.answers.map((answer) => (
        <Answer holdAnswer={props.holdAnswer} answer={answer} key={answer.id} />
      ))}
    </section>
  );
}

QuestionSet.js

export default function QuestionSet(props) {
  return (
    <section className="quest">
      <p>{props.question}</p>
    </section>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 06:15:44