State Not Captured Properly and Timer Causing Re-renders in Next.js App #73949
Replies: 3 comments
-
|
I am not sure about point 1, yet, maybe it is missing some more info. Regarding point 2, that's to be expected. Moving it into a custom hook won't do anything either, it is as good as being inlined. The, so called, standard rendering behavior is to re-render a React tree, from the component where state is set. In this case, People usually use this as workshop/demo material, showing how, one has to collocate state, where it is needed. See in that example, there's a number calculation going on, but also, it shows ticking time, and mouse position. That makes the number calculation re-run every second, and every time the mouse moves. The solution in that case, is to notice that these are independent pieces of state, and collocate them with the UI that uses them, further down in the React tree. For your case, though, you want to know the time since the user starts, so why not just store the |
Beta Was this translation helpful? Give feedback.
-
|
For the first issue, I would investigate whether The summary is rendered only when both conditions are true: if (showSummary && quizResults)In const results = await saveQuizProgress(...)
setQuizResults(results)
setShowSummary(true)If I would add logging for: console.log('saveQuizProgress result:', results)
console.log('showSummary:', showSummary)
console.log('quizResults:', quizResults)I would also check whether Could you share the implementation of |
Beta Was this translation helpful? Give feedback.
-
|
There are a few separate things going on here. The two symptoms are actually linked through how Issue 1 — Finish shows the quiz again on the first attempt, but works on retakeTwo root causes combine here: (a) Your summary render is gated on the return value of an async save. if (showSummary && quizResults) {
return <QuizSummary ... />
}
Fix: set const finishingRef = useRef(false)
const handleFinish = async () => {
if (finishingRef.current || isQuizCompleted) return
finishingRef.current = true
// stop timers immediately so they can't fire mid-finish
if (timeSpentTimerRef.current) clearInterval(timeSpentTimerRef.current)
if (questionTimerRef.current) clearInterval(questionTimerRef.current)
setIsQuizCompleted(true)
const totalPoints = questions.reduce((sum, q) => sum + q.points, 0)
try {
const results = await saveQuizProgress(
userId, quiz.id, answers, timeSpent, totalPoints, isDailyChallenge
)
setQuizResults(results ?? { pointsEarned: 0, totalPoints })
} catch (error) {
console.error('Error saving quiz progress:', error)
setQuizResults({ pointsEarned: 0, totalPoints })
toast({ title: "Error", description: "Failed to save progress.", variant: "destructive" })
} finally {
setShowSummary(true)
finishingRef.current = false
}
}Then relax the guard to The (b) const handleRetake = () => {
setCurrentQuestion(0)
setAnswers({})
setIsCorrect(null)
setTimeSpent(0)
setTimeRemaining(questions[0]?.timeLimit)
setShowSummary(false)
setQuizResults(null)
setIsQuizCompleted(false) // <-- was missing
setIsTimerExpired(false) // <-- was missing
}Without these, Issue 2 — constant re-renders from the timerThis part is mostly expected, not a bug: Moving the timer into a custom hook didn't help because the hook's state still lives in function ElapsedTime({ running }: { running: boolean }) {
const [t, setT] = useState(0)
useEffect(() => {
if (!running) return
const id = setInterval(() => setT(p => p + 1), 1000)
return () => clearInterval(id)
}, [running])
return <span>{t}s</span>
}Now only Two more cleanups while you're in there:
After (a) + (b), the first-attempt finish should land on the summary reliably. If it still jumps to the first question specifically, share |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Summary
So basically I am facing two issues with this piece of code.
Finish Button Behavior: When the user clicks on the "Finish" button (handleFinish) for the first time, they are redirected to the start of the same page instead of the summary page. However, in the case of a retake, clicking on the "Finish" button correctly displays the summary page. It seems the issue is related to the state not being properly captured or updated during the initial attempt, but I'm unable to pinpoint the exact problem.
Constant Re-renders: The page is undergoing constant re-renders, which is evident from the console logs, even though there is no user interaction on the page. I suspect this is related to the running timer. I tried moving the timer logic to a custom hook to prevent re-renders, but that didn’t resolve the issue. While this behavior isn’t visible to a normal user, it is noticeable in the console logs.
I am using NextJs 14.2. Could you please help me with this issue?
Additional information
No response
Example
No response
Beta Was this translation helpful? Give feedback.
All reactions