如何跟踪用户何时在 React 的测验应用程序中选择新答案

问题描述 投票:0回答:1

我正在开发一个测验应用程序,它从 API 获取问题和答案。

我想要实现的是检查在同一问题下,如果用户单击正确的答案,则将分数增加 1,如果他们改变主意并选择另一个错误的答案,那就是他们的最终答案,从分数中删除 1。

我尝试在

checkEachAnswer
函数中实现这一点,但我遇到了一个逻辑错误,当您单击正确的选项时,它会递增,更改答案并递减,但它以一种没有意义的方式执行此操作,即如果你先选择了正确答案,然后改成错误答案,它就会减1,当你来回测试所有问题时,到一天结束时,即使你选择了4个正确答案,你最终可能会得到0作为你的总分。

代码沙箱

测验组件代码如下:

import { useEffect, useState } from "react";


export default function Quiz() {

  const [quiz, setQuiz] = useState(null);
  const [playAgain, setPlayAgain] = useState(false);
  const [togglePlayAgain, setTogglePlayAgain] = useState(playAgain); // Included this when I encountered a bug whereby the useEffect was being called when I set its dependency array to the value of playAgain initially. Shout out to the nice folks on Stack Overflow.

  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);
  const [userAnswer, setUserAnswer] = useState([]);
  const [showAnswer, setShowAnswer] = useState(false);
  const [showAnswerBtn, setShowAnswerBtn] = useState(true);
  const [active, setActive] = useState(true);
  const [score, setScore] = useState(0);
  const [buttonClickable, setButtonClickable] = useState(true);
  

  // This hook fetches data once
  // Added error handling to prevent errors filling up the UI
  useEffect(() => {
    fetch("https://opentdb.com/api.php?amount=5&category=18&difficulty=easy&type=multiple")
      .then(result => {
        if (!result.ok) {
          throw new Error("This is an HTTP error", result.status);
        }
        else {
          return result.json();
        }
      })
      .then(data => {
        // Had to move this here because we only want to call the randomize function once. Doing otherwise results in bugs like the options switching position everytime we click on any.
        const modifiedQuiz = data.results.map(eachQuiz => {
          const incorrectOptions = eachQuiz.incorrect_answers;
          const correctOption = eachQuiz.correct_answer;
          const options = incorrectOptions.concat(correctOption);
          const randomOptions = createRandomOptions(options);
          
          return {
            ...eachQuiz,
            options: randomOptions,
            correctOption: correctOption,
            clickedOptionIndex: -1, // Tracks the index of the clicked option in each question. Set to minus -1 to show that no option has been clicked yet.
            userAnswerPerQuiz: [],
          };
        });
        setQuiz(modifiedQuiz);
      })
      .catch(error => {
        console.error("An error occurred!", error);
        setQuiz(null);
        setError(true);
      })
      .finally(() => {
        setLoading(false);
      });
    
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [togglePlayAgain])



  // Shuffles both the incorrect and correct answers
  function createRandomOptions(arr) {
      
    let copyOptions = [...arr];
    let randomOptionsArr = [];

    while (copyOptions.length > 0) {
      let randomIndex = Math.floor(Math.random() * copyOptions.length);
      randomOptionsArr.push(copyOptions[randomIndex]);
      copyOptions.splice(randomIndex, 1); 
    }

    return randomOptionsArr;
  }


  // Helps check for a click on our options and handles necessary logic
  function handleClick(option, correctAnswer, position, questionIndex, event) {
    checkEachAnswer(option, correctAnswer);
    setActive(false);
    setButtonClickable(true);

    // Checks if the index of the current question when clicked is the index of the quiz rendered initially
    const updatedQuiz = quiz.map((eachQuiz, index) =>  
      index === questionIndex 
      ? {...eachQuiz, clickedOptionIndex: position} 
      : eachQuiz
    );
    setQuiz(updatedQuiz);
  }

  console.log(score);

  // Checks if the clicked option is the correct one and also checks if it was already picked before and prevents it from being added to the userAnswer array
  function checkEachAnswer(option, correctAnswer) {

    if (option === correctAnswer) {
      setScore(score + 1);  
    }

    else if (option !== correctAnswer && score > 0) {
      setScore(score - 1);
    }




    /* if (option === correctAnswer && !active) {
      console.log("Correct");
     
      // Check if clicked answer exists before and reset the value back to what was clicked to eliminate same answer repeated in the userAnswer array
      if (userAnswer.includes(option)) {
        let userAnsArrCopy = [...userAnswer];
        let index = userAnsArrCopy.findIndex(elem => elem);
        userAnsArrCopy[index] = option;
        
        setUserAnswer(prevValue => {
          return userAnsArrCopy;
        }); 
      }

      else {
        setUserAnswer(prevValue => {
          return [...prevValue, option];
        });
       
      }

    }
    else {
      console.log(option, "is incorrect", );

      setUserAnswer(prevValue => {
        return [...prevValue, prevValue[optIndex] = option]
      })
    } */

    
  }


  const quizElements = quiz && quiz.map((eachQuiz, questionIndex) => {

    // Destructure each object
    const {question, options, correctOption, clickedOptionIndex} = eachQuiz;

    return (
      <>
        <div className="quiz-wrapper">
          <p className="question">{question}</p>
          <ul>
            {quiz && options.map((option, index) => 
              {
                return (
                  <li>
                  <button
                    className={
                      `option 
                      ${clickedOptionIndex === index
                        ? "active" : "" } 
                      ${showAnswer && option === correctOption
                        ? "correct" : "" }
                      ${showAnswer && option !== correctOption && active
                        ? "wrong" : "" }`
                    }
                    key={index}
                    onClick={(event) => 
                      handleClick(option, correctOption, index, questionIndex, event)}
                    disabled={!buttonClickable}
                  >
                    {option}
                    </button>
                  </li>
                )
              })
            }
          </ul>
        </div>
        <div className="divider"></div>
      </>
    )
  });

  console.log(userAnswer);


  // Displays the answers when we click on the Check Again button
  function displayAnswer() {
    setShowAnswer(true);
    setPlayAgain(true);
    setShowAnswerBtn(false);
    setButtonClickable(false);
  }


  // Responsible for the Play Again button
  function updatePlayAgain() {
    setTogglePlayAgain(!togglePlayAgain);
    setPlayAgain(false);
    setShowAnswer(false);
    setShowAnswerBtn(true);
    setUserAnswer([]);
    setScore(0);
    setButtonClickable(true);
  }

  return (
    <>
      {loading && <h3>Currently loading...</h3>}
      {error && <h3>An error occurred while fetching data! Please check your network connection</h3>}
      {quiz && <h1 className="topic">Topic: Computer Science</h1>}
      
      {quiz && quizElements}

      {showAnswer && <p>You scored {score} / {quiz.length}</p>}

      {quiz && showAnswerBtn && 
        <button 
          onClick={() => displayAnswer()}
          className="main-btn"
        >
          Check Answer
        </button>
      }

      {quiz && playAgain && 
        <button 
          onClick={() => updatePlayAgain()}
          className="main-btn"
        >
          Play Again
        </button>
      }
    </>
  )
}

有人知道可能出了什么问题吗?

谢谢你。

javascript reactjs logic
1个回答
0
投票

在下面的示例中,您可以跟踪用户选择选项的次数。

提交时:

  • 如果点击次数为 0
    • 他们没有做出回应...
  • 如果他们的点击次数为 1
    • 如果正确,奖励积分
    • 错了扣分
  • 如果他们的点击次数 > 1
    • 如果正确,请勿奖励积分
    • 错了扣分

const { useCallback, useEffect, useState } = React;

class HTMLDecoder {
  constructor() {
    this.parser = new DOMParser();
  }
  decode(input) {
    const doc = this.parser.parseFromString(input, 'text/html');
    return doc.documentElement.textContent;
  }
}

const randomize = () => 0.5 - Math.random();

const decoder = new HTMLDecoder();

const defaultQuizParams = {
  amount: 5,
  category: 18, // "Science: Computers"
  difficulty: 'easy',
  type: 'multiple'
};

const fetchQuiz = (quizParams = {}) => {
  const params = { ...defaultQuizParams, ...quizParams };
  const url = new URL('https://opentdb.com/api.php');
  for (let param in params) {
    url.searchParams.append(param, params[param]);
  }
  return fetch(url.href).then(res => res.json());
}

const processQuestion = ({ correct_answer, incorrect_answers, question }) => ({
  answer: correct_answer,
  choices: [...incorrect_answers, correct_answer].sort(randomize),
  clicks: 0,
  prompt: question,
  uuid: self.crypto.randomUUID()
});

const Choice = ({ onChange, uuid, value }) => {
  return (
    <React.Fragment>
      <input type="radio" name={uuid} value={value} onChange={onChange} />
      <label>{decoder.decode(value)}</label>
    </React.Fragment>
  );
}

const Question = ({ answer, choices, clicks, onChange, prompt, uuid }) => {
  return (
    <div className="Question" data-uuid={uuid}>
      <h2>{decoder.decode(prompt)} ({clicks})</h2>
      <ol>
        {choices.map((choice) => (
          <li key={choice}>
            <Choice onChange={onChange} uuid={uuid} value={choice} />
          </li>
        ))}
      </ol>
    </div>
  );
};

const Quiz = ({ onChange, questions }) => {
  return (
    <div className="Quiz">
      {questions.map(({ answer, choices, clicks, prompt, uuid }) => {
        return (
          <Question
            key={uuid}
            answer={answer}
            choices={choices}
            clicks={clicks}
            onChange={onChange}
            prompt={prompt}
            uuid={uuid} />
        );
      })}
    </div>
  );
}

const App = () => {
  const [questions, setQuestions] = useState([]);

  useEffect(() => {
    fetchQuiz().then(({ results }) => {
      setQuestions(results.map(processQuestion));
    })
  }, []);
  
  const handleChange = useCallback((e) => {
    setQuestions((oldValue) => {
      const newValue = structuredClone(oldValue);
      const question = newValue.find(({ uuid }) => uuid === e.target.name);
      question.clicks++;
      return newValue;
    });
  }, []);

  return (
    <Quiz onChange={handleChange} questions={questions} />
  );
};

ReactDOM
  .createRoot(document.getElementById("root"))
  .render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

© www.soinside.com 2019 - 2024. All rights reserved.