Nguyên nhân: khi app vào background, hệ điều hành tạm dừng hoặc giới hạn JS thread, nên setInterval ngừng chạy. Nếu mỗi giây trừ remaining - 1, quay lại app sẽ thấy đồng hồ đứng yên hoặc lệch.
Cách đúng: lưu mốc thời gian kết thúc (deadline), không lưu số giây còn lại. Mỗi tick và khi app quay lại foreground, tính lại từ đồng hồ hệ thống:
const [endAt] = useState(() => Date.now() + 120_000)
const [left, setLeft] = useState(120)
useEffect(() => {
const tick = () => setLeft(Math.max(0, Math.ceil((endAt - Date.now()) / 1000)))
const id = setInterval(tick, 1000)
// Recompute immediately when the app becomes active again
const sub = AppState.addEventListener('change', (s) => s === 'active' && tick())
return () => { clearInterval(id); sub.remove() }
}, [endAt])Những điểm hay bị hỏi thêm:
- Cần báo khi hết giờ dù app đang nền → đặt local notification hẹn giờ, đừng trông vào timer JS.
- Deadline cần sống qua việc app bị kill → lưu endAt vào storage.
- OTP/giữ chỗ: server mới là nơi quyết định hết hạn; đồng hồ trên máy chỉ để hiển thị.
Lưu ý: người dùng có thể chỉnh giờ máy, nên logic nghiệp vụ (còn hiệu lực hay không) luôn kiểm tra lại ở server.