首页 > 解决方案 > 为什么我的单选输入只能在数组映射的整个数组列表中选择一个选项?

问题描述

这是我第三次问,因为之前的2个帖子没有任何答案,因为我真的真的在这个问题上卡了整整几天,所以请原谅我,因为我已经尝试了几种方法但是它一直不像我想要的那样工作。

有人可以帮帮我吗,我使用数组映射根据 question_ID 列出我所有的问题和答案

这是我使用 Axios 获取的 json。

[
    {
        "question_ID": 13,
        "question_desc": "This is question 1",
        "quiz_ID": 6,
        "answer_ID": 17,
        "answer_desc": "A",
        "answer_result": 1
    },
    {
        "question_ID": 13,
        "question_desc": "This is question 1",
        "quiz_ID": 6,
        "answer_ID": 18,
        "answer_desc": "B",
        "answer_result": 1
    },
    {
        "question_ID": 13,
        "question_desc": "This is question 1",
        "quiz_ID": 6,
        "answer_ID": 19,
        "answer_desc": "C",
        "answer_result": 0
    },
    {
        "question_ID": 14,
        "question_desc": "Question 2",
        "quiz_ID": 6,
        "answer_ID": 20,
        "answer_desc": "A",
        "answer_result": 0
    },
    {
        "question_ID": 14,
        "question_desc": "Question 2",
        "quiz_ID": 6,
        "answer_ID": 21,
        "answer_desc": "B",
        "answer_result": 1
    },
    {
        "question_ID": 15,
        "question_desc": "Question 3",
        "quiz_ID": 6,
        "answer_ID": 22,
        "answer_desc": "A",
        "answer_result": 1
    },
    {
        "question_ID": 15,
        "question_desc": "Question 3",
        "quiz_ID": 6,
        "answer_ID": 23,
        "answer_desc": "B",
        "answer_result": 0
    },
    {
        "question_ID": 16,
        "question_desc": "Question 4",
        "quiz_ID": 6,
        "answer_ID": 24,
        "answer_desc": "A",
        "answer_result": 0
    },
    {
        "question_ID": 16,
        "question_desc": "Question 4",
        "quiz_ID": 6,
        "answer_ID": 25,
        "answer_desc": "B",
        "answer_result": 1
    },
    {
        "question_ID": 17,
        "question_desc": "Question 5",
        "quiz_ID": 6,
        "answer_ID": 26,
        "answer_desc": "Testing Answer",
        "answer_result": 0
    }
]

然后我使用reduce对问题进行分组,例如,它会像Question_ID:13,Question_ID:14,每个里面都有一个答案列表

在此处输入图像描述

控制台中显示的是当我按下下一个按钮时,它会在我刚刚用来减少分组的分组问题数组中来回移动。

然后我映射该分组问题以显示无线电输入,它显示得非常好。

但问题是当我点击收音机时,例如问题 1,我选择了我想要的 3 个答案中的 1 个

在此处输入图像描述

然后我按 next 转到分组数组的下一个索引以显示新的答案列表,然后我重复选择我想要的答案

在此处输入图像描述

问题开始出现,如果我按Previous更改或回顾以前的答案,它就会消失,因为我只能在我的数组列表中选择1,请帮助解释,因为我不明白。

之前的答案消失了

在此处输入图像描述

这是我的代码:

import axios from "axios";
import { Component } from "react";
import jwt_decode from "jwt-decode";

class QuizForm extends Component {
    constructor(props) {
        super(props);
        this.state = {
            step: 0,
            dataQuestion: [],
            question: 1,
            answer: [],
            groupQues: [],
            selectedRadio: null,
        };
    }
    // ------------------

// Get list of questions + answers based on the Topic ID//
    async componentDidMount() {
        await axios
            .get("http://localhost:3000/quiz/" + this.props.quizID)
            .then((res) => {
                this.setState({
                    dataQuestion: res.data,
                });
            })

// Then I group it like the picture from above using reduce//
            .then(() => {
                let grouped = this.state.dataQuestion.reduce((acc, obj) => {
                    const property = obj["question_ID"];
                    acc[property] = acc[property] || [];
                    acc[property].push(obj);
                    return acc;
                }, {});
                this.setState({ groupQues: grouped });
            })
            .then(() => {
                var token = localStorage.token;
                var decoded = jwt_decode(token);
            })
            .catch((error) => console.log(error));
    }
    // -------------------
// The next and previous button to handle switching index in the array //
    handleNext = (answer_ID) => {
        let grouped = this.state.dataQuestion.reduce((acc, obj) => {
            const property = obj["question_ID"];
            acc[property] = acc[property] || [];
            acc[property].push(obj);
            return acc;
        }, {});

        if (this.state.step === Object.keys(grouped).length - 1) {
            return;
        }
        this.setState({ step: this.state.step + 1 });
        this.setState({ question: this.state.question + 1 });

        // this.setState({selectedRadio: null})
    };
    handlePrevious = (answer_ID) => {
        if (this.state.step === 0) {
            return;
        }
        this.setState({ step: this.state.step - 1 });
        this.setState({ question: this.state.question - 1 });
    };
    handleSelect = (answer_ID) => {
        this.setState({ selectedRadio: answer_ID });
    };
    // ---------------------
    render() {
        const { question } = this.state;

        return (
            <>
                <div className="column middle">
                    <div className="game-details-container">
                        <h1>
                            {" "}
                            Question : <span id="question-number"></span>
                            {question}/{Object.keys(this.state.groupQues).length}
                        </h1>
                    </div>
                    <div className="game-quiz-container">
                        {
                            this.state.groupQues[
                                Object.keys(this.state.groupQues)[this.state.step]
                            ]?.[0].question_desc
                        }
                        <div className="game-options-container">
// In here I'm using map to get list out an array of answers based on question_ID
                            {this.state.groupQues[
                                Object.keys(this.state.groupQues)[this.state.step]
                            ]?.map((element, i) => {
                                return (
                                    <div key={i}>
                                        <input
                                            type="radio"
                                            checked={this.state.selectedRadio === element.answer_ID}
                                            onChange={() => this.handleSelect(element.answer_ID)}
                                            id={element.answer_ID}
                                            name={element.question_desc}
                                            value={element.answer_result}
                                        />
                                        <label htmlFor={element.question_desc}>
                                            {element.answer_desc}
                                        </label>
                                    </div>
                                );
                            })}
                        </div>
                        <div className="next-button-container">
                            <button onClick={() => this.handlePrevious()}>
                                Previous Question
                            </button>
                            <button onClick={() => this.handleNext()}>Next Question</button>
                        </div>
                    </div>
                </div>
            </>
        );
    }
}
export default QuizForm;

标签: reactjs

解决方案


如果我理解正确,您希望准确跟踪用户对所有问题的回答(即使是“下一个问题”和“上一个问题”的点击)。

但是,在 中handleSelect,您会覆盖this.state.selectedRadio用户当前提交的内容(这意味着您会忘记用户之前可能点击过的内容)。

更好的方法可能是将组件的数据/状态重组为一个数组,其中每个项目(在数组中)是一个包含以下内容的对象:

  • 问题数据
  • 该特定问题的选项
  • 用户为该特定问题选择的答案

每个对象可能看起来像:

{
    question_ID: number,
    question_desc: string,
    quiz_ID: string,
    answers: [
        {
            answer_ID: number,
            answer_desc: string,
            answer_result: number,
        },
    ],
    answerSubmittedByUser: number, // This is where you keep track of what the user answered for this question. For example, you can store `answer_ID` here.
}

然后,您还可以使用indexor cursor(作为组件状态)来跟踪用户当前所在的问题/对象。这样,当用户单击Next question或时Previous question,您只需递增/递减index(同时还确保新索引不会超出数组的范围)。同样,如果他们选择了答案,您就知道要更新哪个索引(在数组中)。

这是一个示例实现QuizForm。虽然它不使用基于类的组件,但它仍然应该给你一个想法。

import React, { useEffect, useState } from 'react';

export default function QuizForm() {
  const [questions, setQuestions] = useState([]);
  const [questionIndex, setQuestionIndex] = useState(0);

  useEffect(() => {
    let hasUnmounted = false;

    async function fetchTransformAndSetQuestionsAsState() {
      const questions = await getDataForQuiz();

      if (hasUnmounted) {
        return;
      }

      const groupedByQuestionId = questions.reduce((acc, obj) => {
        const property = obj['question_ID'];
        acc[property] ?? (acc[property] = []);
        acc[property].push(obj);
        return acc;
      }, {});

      const readiedForState = Object.values(groupedByQuestionId).map(questionData => {
        return {
          question_ID: questionData[0].question_ID,
          question_desc: questionData[0].question_desc,
          quiz_ID: questionData[0].quiz_ID,
          answers: questionData.map(question => {
            return {
              answer_ID: question.answer_ID,
              answer_desc: question.answer_desc,
              answer_result: question.answer_result,
            };
          }),
          answerSubmittedByUser: null,
        };
      });

      setQuestions(readiedForState);
    }

    fetchTransformAndSetQuestionsAsState();

    return () => {
      hasUnmounted = true;
    };
  }, []);

  if (0 === questions.length) {
    return <p>Loading questions... just a moment please.</p>;
  }

  const currentQuestion = questions[questionIndex];
  const [isFirstQuestion, isLastQuestion] = [0 === questionIndex, questions.length - 1 === questionIndex];

  const handlePreviousQuestionClick = () => {
    setQuestionIndex(prev => Math.max(0, prev - 1));
  };

  const handleNextQuestionClick = () => {
    setQuestionIndex(prev => Math.min(questions.length - 1, prev + 1));
  };

  const handleAnswer = (questionId, answerId) => {
    setQuestions(prev => {
      /**
       * It's probably okay to use questionIndex directly. The ternary and IF check below are probably unnecessary.
       * Have written like this just in case we close over a stale `questionIndex`, even though that's probably very unlikely.
       */
      const indexToUpdate = prev[questionIndex].question_ID === questionId ? questionIndex : prev.findIndex(({ question_ID }) => questionId === question_ID);

      if (-1 !== indexToUpdate) {
        return [...prev.slice(0, indexToUpdate), { ...prev[indexToUpdate], answerSubmittedByUser: answerId }, ...prev.slice(indexToUpdate + 1)];
      }

      return prev; // Unlikely that this line will ever run.
    });
  };

  return (
    <>
      <div className="column middle">
        <div className="game-details-container">
          <h1>
            <span>Question: </span>
            <span id="question-number">{questionIndex + 1}</span>
            <span>{`/${questions.length}`}</span>
          </h1>
        </div>
        <div className="game-quiz-container">
          <p>{currentQuestion.question_desc}</p>
          <div className="game-options-container">
            {currentQuestion.answers.map(answer => {
              return (
                <div key={answer.answer_ID}>
                  <input
                    type="radio"
                    checked={answer.answer_ID === currentQuestion.answerSubmittedByUser}
                    onChange={() => handleAnswer(currentQuestion.question_ID, answer.answer_ID)}
                    id={answer.answer_ID}
                    name={answer.question_desc}
                    value={answer.answer_result}
                  />
                  <label htmlFor={answer.answer_ID}>{answer.answer_desc}</label>
                </div>
              );
            })}
          </div>
          <div className="next-button-container">
            <button onClick={handlePreviousQuestionClick} disabled={isFirstQuestion}>
              Previous Question
            </button>
            <button onClick={handleNextQuestionClick} disabled={isLastQuestion}>
              Next Question
            </button>
          </div>
        </div>
      </div>
    </>
  );
}

async function getDataForQuiz() {
  return [
    { question_ID: 13, question_desc: 'This is question 1', quiz_ID: 6, answer_ID: 17, answer_desc: 'A', answer_result: 1 },
    { question_ID: 13, question_desc: 'This is question 1', quiz_ID: 6, answer_ID: 18, answer_desc: 'B', answer_result: 1 },
    { question_ID: 13, question_desc: 'This is question 1', quiz_ID: 6, answer_ID: 19, answer_desc: 'C', answer_result: 0 },
    { question_ID: 14, question_desc: 'Question 2', quiz_ID: 6, answer_ID: 20, answer_desc: 'A', answer_result: 0 },
    { question_ID: 14, question_desc: 'Question 2', quiz_ID: 6, answer_ID: 21, answer_desc: 'B', answer_result: 1 },
    { question_ID: 15, question_desc: 'Question 3', quiz_ID: 6, answer_ID: 22, answer_desc: 'A', answer_result: 1 },
    { question_ID: 15, question_desc: 'Question 3', quiz_ID: 6, answer_ID: 23, answer_desc: 'B', answer_result: 0 },
    { question_ID: 16, question_desc: 'Question 4', quiz_ID: 6, answer_ID: 24, answer_desc: 'A', answer_result: 0 },
    { question_ID: 16, question_desc: 'Question 4', quiz_ID: 6, answer_ID: 25, answer_desc: 'B', answer_result: 1 },
    { question_ID: 17, question_desc: 'Question 5', quiz_ID: 6, answer_ID: 26, answer_desc: 'Testing Answer', answer_result: 0 },
  ];
}

旁注:您可以将问题获取和转换逻辑封装到自定义的 React 钩子中(命名useQuiz为自定义钩子)。


推荐阅读