首页 > 解决方案 > 状态不在使用 useState 的函数之间保持不变

问题描述

我正在练习 React useState hooks 来做一个每个问题十秒计时器的测验。

目前,我能够从 API 获取测验问题,将它们设置为状态,并呈现它们。如果用户单击答案,则问题将从状态数组中删除,状态秒数重置为 10 并呈现下一个问题。

当状态中的问题数组中没有任何内容时,我试图让计时器清除。当我在 startTimer 函数中使用 console.log(questions) 时,它是一个空数组,尽管相同的 console.log 在 userAnswer 函数中显示数据?我在这里想念什么?

我删除了引用的 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;

标签: javascriptreactjsuse-state

解决方案


您的每个渲染都App将创建其内部函数和变量(如questionsstartTimer)的新绑定。

当按钮被单击并getQuestions运行时,getQuestions然后调用该startTimer函数。在那个时间点,该startTimer函数关闭了在questions单击按钮时定义的变量——但此时它是空的。在区间内,虽然函数已经重新渲染,但区间回调仍然引用questions旧闭包中的 。

如果填充了数组,我将启动一个 setTimeouton render (在 a 内,useLayoutEffect以便在需要时可以清除超时) ,而不是依赖于旧的闭包。questions

另一个问题是splice不应该与 React 一起使用,因为它会改变现有的数组 - 使用非变异方法,比如slice.

尝试以下操作:

// 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")
}

推荐阅读