[英]State not persisting between functions with useState
I am practicing React useState hooks to make a quiz with a ten second timer per question.我正在练习使用 React useState hooks 来制作每个问题 10 秒计时器的测验。
Currently I am able to get the quiz questions from an API, set them to state, and render them.目前我能够从 API 获取测验问题,将它们设置为状态,并呈现它们。 If a user clicks an answer, the question is removed from the array in state, the seconds in state is reset to 10 and the next question renders.
如果用户单击答案,则问题将从状态数组中删除,状态秒数重置为 10 并呈现下一个问题。
I am trying to get the timer to clear when there is nothing left in the array of questions in state.当状态问题数组中没有任何内容时,我试图让计时器清除。 When I console.log(questions) in the startTimer function, it is an empty array, despite the same console.log showing the data in the userAnswer function?
当我在 startTimer 函数中使用 console.log(questions) 时,它是一个空数组,尽管相同的 console.log 显示了 userAnswer 函数中的数据? What am I missing here?
我在这里缺少什么?
I've removed the referenced shuffle function to save space我已经删除了引用的 shuffle 函数以节省空间
function App() {
// State for trivia questions, time left, right/wrong count
const [questions, setQuestions] = useState([])
const [seconds, setSeconds] = useState(10);
const [correct, setCorrect] = useState(0);
const [incorrect, setIncorrect] = useState(0);
// Get data, set questions to state, start timer
const getQuestions = async () => {
let trivia = await axios.get("https://opentdb.com/api.php?amount=10&category=9&difficulty=easy&type=multiple")
trivia = trivia.data.results
trivia.forEach(result => {
result.answers = shuffle([...result.incorrect_answers, result.correct_answer])
})
setQuestions(trivia)
startTimer()
}
const startTimer = () => {
// Empty array here, but data at beginning of userAnswer
console.log(questions)
const interval = setInterval(() => {
// If less than 1 second, increment incorrect, splice current question from state, clear interval and start timer back at 10
setSeconds(time => {
if (time < 1) {
setIncorrect(wrong => wrong + 1)
setQuestions(q => {
q.splice(0,1)
return q;
})
clearInterval(interval);
startTimer()
return 10;
}
// I want this to hold the question data, but it is not
if (questions.length === 0) {
console.log("test")
}
// Else decrement seconds
return time - 1;
});
}, 1000);
}
// If answer is right, increment correct, else increment incorrect
// Splice current question from const questions
const userAnswer = (index) => {
// Shows array of questions here
console.log(questions)
if (questions[0].answers[index] === questions[0].correct_answer) {
setCorrect(correct => correct + 1)
} else {
setIncorrect(incorrect => incorrect + 1)
}
setQuestions(q => {
q.splice(0,1);
return q;
})
// Shows same array here despite splice in setQuestions above
console.log(questions)
setSeconds(10);
}
return (
<div>
<button onClick={() => getQuestions()}></button>
{questions.length > 0 &&
<div>
<Question question={questions[0].question} />
{questions[0].answers.map((answer, index) => (
<Answer
key={index}
answer={answer}
onClick={() => userAnswer(index)} />
))}
</div>
}
<p>{seconds}</p>
<p>Correct: {correct}</p>
<p>Incorrect: {incorrect}</p>
</div>
)
}
export default App;
Each render of your App
will create new bindings of its inner functions and variables (like questions
and startTimer
). App
每次渲染都会为其内部函数和变量(如questions
和startTimer
)创建新的绑定。
When the button is clicked and getQuestions
runs, getQuestions
then calls the startTimer
function.当点击该按钮
getQuestions
运行, getQuestions
然后调用startTimer
功能。 At that point in time, the startTimer
function closes over the questions
variable that was defined at the time the button was clicked - but it's empty at that point.在那个时间点,
startTimer
函数关闭在单击按钮时定义的questions
变量 -但那时它是空的。 Inside the interval, although the function has re-rendered, the interval callback is still referencing the questions
in the old closure.在区间内部,虽然函数重新渲染了,区间回调仍然引用旧闭包中的
questions
。
Rather than depending on old closures, I'd initiate a setTimeout
on render (inside a useLayoutEffect
so the timeout can be cleared if needed) if the questions
array is populated.如果填充了
questions
数组,我将在渲染时启动setTimeout
(在useLayoutEffect
以便在需要时可以清除超时),而不是依赖于旧的闭包。
Another issue is that splice
should not be used with React, since that mutates the existing array - use non-mutating methods instead, like slice
.另一个问题是
splice
不应该与 React 一起使用,因为它会改变现有数组 - 改用非变异方法,例如slice
。
Try something like the following:尝试类似以下内容:
// Get data, set questions to state, start timer
const getQuestions = async () => {
let trivia = await axios.get("https://opentdb.com/api.php?amount=10&category=9&difficulty=easy&type=multiple")
trivia = trivia.data.results
trivia.forEach(result => {
result.answers = shuffle([...result.incorrect_answers, result.correct_answer])
})
setQuestions(trivia);
setSeconds(10);
}
useLayoutEffect(() => {
if (!questions.length) return;
// Run "timer" if the quiz is active:
const timeoutId = setTimeout(() => {
setSeconds(time => time - 1);
}, 1000);
return () => clearTimeout(timeoutId);
});
// If quiz is active and time has run out, mark this question as wrong
// and go to next question:
if (questions.length && time < 1) {
setIncorrect(wrong => wrong + 1)
setQuestions(q => q.slice(1));
setSeconds(10);
}
if ((correct || incorrect) && !questions.length) {
// Quiz done
console.log("test")
}
This is because of closure .这是因为关闭。
When getQuestions
executes this part:当
getQuestions
执行这部分时:
setQuestions(trivia)
startTimer()
Both functions run in the same render cycle.这两个函数在同一个渲染周期中运行。 In this cycle
questions
is still []
(or whatever previous value it held).在这个循环中,
questions
仍然是[]
(或者它之前持有的任何值)。 So when you call console.log(questions)
inside startTimer
, you get an empty array.因此,当您在
startTimer
调用console.log(questions)
,您会得到一个空数组。
You can fix that by invoking startTimer
in the next render cycle with useEffect
您可以通过在下一个渲染周期中使用
useEffect
调用startTimer
来解决这个useEffect
const [ start, setStart ] = useState(false)
const getQuestions = async () => {
let trivia = await axios.get("https://opentdb.com/api.php?amount=10&category=9&difficulty=easy&type=multiple")
trivia = trivia.data.results
trivia.forEach(result => {
result.answers = shuffle([...result.incorrect_answers, result.correct_answer])
})
setQuestions(trivia)
setStart(true)
}
useEffect(()=>{
if (start) {
startTimer();
setStart(false)
}
},[start])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.