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

React点击答题选项时答案数组重复洗牌问题求助

问题描述

我是React新手,开发了一个<QuizPage />组件,包含子组件<TriviaObject />,子组件接收题目、正确答案(字符串)、错误答案数组三个属性。API返回的数据模型示例:

{
  "question": "question",
  "correct_answer": "answer",
  "incorrect_answers": [ "answer1", "answer2", "answer3", ... ]
}

当前遇到两个问题:

  • 点击任意答案选项时,页面上的答案数组会重新洗牌,期望点击仅将选项存入userSelection状态,用于后续提交统计正确率。
  • 代码中添加了多处console.log但控制台无输出,怀疑状态或useEffect未正常工作。

新增userSelection状态存储每题用户选择后出现了上述问题,以下是代码实现:

QuizPage组件代码

export default function QuizPage() {
    const [quiz, setQuiz] = useState([]);
    // state to store user's selected answer for each question
    const [userSelection, setUserSelection] = useState([])

    useEffect(() => {
        async function getData() {
            try {
                const res = await fetch("https://opentdb.com/api.php?amount=10")
                const data = await res.json()
                setQuiz(data.results)
            } catch (error) {
                console.error("Error fetching data:", error)
            }
        }
        getData()
    }, [])
    
    if (!quiz || quiz.length === 0) {
        return <div>Loading...</div>; // or any loading indicator
    }

    const handleAnswerSelected = (answer, index) => {
        console.log(`Selected Answer for Question ${index + 1}:`, answer)
        // update the userSelection state array with the selected answer
        const updatedSelection = [...userSelection];
        updatedSelection[index] = answer;
        setUserSelection(updatedSelection);
        console.log("Update userSelection:", updatedSelection)
    }

    const renderTrivia = quiz.map((object, index) => {
        console.log("Rendering TriviaObject with props:", object)
        return (
        <TriviaObject
            key={index}
            question={decodeHTML(object.question)}
            correct_answer={decodeHTML(object.correct_answer)}
            incorrect_answers={object.incorrect_answers.map(item => decodeHTML(item))}
            onAnswerSelected={(answer) => handleAnswerSelected(answer, index)}
            
        />
    )})

    return (
        <div className="quiz-page">
        {renderTrivia}
        <button>Submit Answers</button>
        </div>
    )
}

TriviaObject组件代码

export default function TriviaObject(props) {
    const { question, onAnswerSelected, correct_answer, incorrect_answers } = props;

    const [allAnswers, setAllAnswers] = useState([]);

    // Apply shuffle only once when the component mounts
    useEffect(() => {
        console.log("shuffling answers...");
        const shuffledArray = shuffle([correct_answer, ...incorrect_answers]);
        setAllAnswers(shuffledArray);
    }, [correct_answer, incorrect_answers]);
    
    // Fisher-Yates shuffle alrgorithm
    function shuffle(array) {
        const shuffledArray = [...array];
        for (let i = shuffledArray.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            const temp = shuffledArray[i];
            shuffledArray[i] = shuffledArray[j];
            shuffledArray[j] = temp;
        }
        return shuffledArray;
    }

    return (
    <div className="trivia-obj">
        <h2>{question}</h2>
        <ul>
        {allAnswers.map((answer, index) => {
            const styles = { 
                backgroundColor:
                    onAnswerSelected[index] === answer ? '#D6DBF5' : '' ,
                border:
                    onAnswerSelected[index] === answer
                        ? 'none'
                        : '1px solid #D6DBF5'
            };
            return (
                <li
                    key={index}
                    onClick={() => onAnswerSelected(answer)}
                    style={styles}
                >
                    {answer}
                </li>
            );
        })}
        </ul>
    </div>
);
}
解决方案

问题根源分析

  1. 答案重新洗牌:每次userSelection更新触发QuizPage重渲染时,incorrect_answers会重新执行map(decodeHTML)生成新数组,导致TriviaObject的useEffect依赖项变化,触发重新洗牌。
  2. console.log无输出:TriviaObject中错误将回调函数onAnswerSelected当作数组访问onAnswerSelected[index],引发静默报错,阻断了日志输出。

修复代码

修改后的QuizPage组件

export default function QuizPage() {
    const [quiz, setQuiz] = useState([]);
    const [userSelection, setUserSelection] = useState([]);

    useEffect(() => {
        async function getData() {
            try {
                const res = await fetch("https://opentdb.com/api.php?amount=10");
                const data = await res.json();
                // 提前解码所有数据,避免每次渲染生成新数组
                const decodedQuiz = data.results.map(item => ({
                    question: decodeHTML(item.question),
                    correct_answer: decodeHTML(item.correct_answer),
                    incorrect_answers: item.incorrect_answers.map(i => decodeHTML(i))
                }));
                setQuiz(decodedQuiz);
            } catch (error) {
                console.error("Error fetching data:", error);
            }
        }
        getData();
    }, []);
    
    if (!quiz || quiz.length === 0) {
        return <div>Loading...</div>;
    }

    const handleAnswerSelected = (answer, index) => {
        console.log(`Selected Answer for Question ${index + 1}:`, answer);
        const updatedSelection = [...userSelection];
        updatedSelection[index] = answer;
        setUserSelection(updatedSelection);
        console.log("Updated userSelection:", updatedSelection);
    };

    const renderTrivia = quiz.map((object, index) => {
        console.log("Rendering TriviaObject with props:", object);
        return (
            <TriviaObject
                key={index}
                question={object.question}
                correct_answer={object.correct_answer}
                incorrect_answers={object.incorrect_answers}
                selectedAnswer={userSelection[index]}
                onAnswerSelected={(answer) => handleAnswerSelected(answer, index)}
            />
        );
    });

    return (
        <div className="quiz-page">
            {renderTrivia}
            <button>Submit Answers</button>
        </div>
    );
}

修改后的TriviaObject组件

import { useMemo } from 'react';

export default function TriviaObject(props) {
    const { question, onAnswerSelected, correct_answer, incorrect_answers, selectedAnswer } = props;

    // 用useMemo缓存洗牌结果,仅在初始数据变化时重新洗牌
    const allAnswers = useMemo(() => {
        console.log("shuffling answers...");
        const shuffle = (array) => {
            const shuffledArray = [...array];
            for (let i = shuffledArray.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
            }
            return shuffledArray;
        };
        return shuffle([correct_answer, ...incorrect_answers]);
    }, [correct_answer, incorrect_answers]);

    return (
        <div className="trivia-obj">
            <h2>{question}</h2>
            <ul>
                {allAnswers.map((answer, index) => {
                    const styles = { 
                        backgroundColor: selectedAnswer === answer ? '#D6DBF5' : '',
                        border: selectedAnswer === answer ? 'none' : '1px solid #D6DBF5'
                    };
                    return (
                        <li
                            key={index}
                            onClick={() => onAnswerSelected(answer)}
                            style={styles}
                        >
                            {answer}
                        </li>
                    );
                })}
            </ul>
        </div>
    );
}

额外优化建议

  • 避免使用数组索引作为key,如果API返回题目唯一标识(如question字段的哈希值),改用唯一标识作为key提升渲染稳定性。
  • 将shuffle函数提取到组件外部,避免每次渲染重复定义函数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 18:45:17