我正在开发一个测验应用程序,它从 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>
}
</>
)
}
有人知道可能出了什么问题吗?
谢谢你。
在下面的示例中,您可以跟踪用户选择选项的次数。
提交时:
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>