实现倒计时功能时React Web应用出现Hydration Error求助
解决React倒计时组件的Hydration Error问题
错误原因
Hydration Error的核心是服务端渲染(SSR)生成的HTML与客户端首次渲染的DOM内容/结构不匹配。你的代码里存在两个关键问题:
useCountdown的初始countDown值直接通过new Date().getTime()计算,服务端和客户端的系统时间、渲染时刻存在差异,导致两边生成的倒计时数值完全不同- 若服务端判断倒计时已过期(渲染
<ExpiredNotice />),但客户端因时间差仍显示倒计时组件,也会触发不匹配校验失败
修复方案
1. 修改useCountdown Hook
调整初始值逻辑,确保服务端渲染时输出统一占位值,客户端挂载后再计算真实倒计时:
import { useEffect, useState } from "react"; const useCountdown = () => { const countDownDate = new Date("May 20, 2023 00:00:00").getTime(); // 服务端渲染时返回0,避免初始值不一致 const [countDown, setCountDown] = useState(() => { if (typeof window === 'undefined') return 0; return countDownDate - new Date().getTime(); }); useEffect(() => { const updateCountdown = () => { setCountDown(countDownDate - new Date().getTime()); }; // 客户端挂载后立即更新一次真实值 updateCountdown(); const interval = setInterval(updateCountdown, 1000); return () => clearInterval(interval); }, [countDownDate]); // 处理倒计时过期的情况,避免负数 const validCountDown = Math.max(countDown, 0); return getReturnValues(validCountDown); }; const getReturnValues = (countDown) => { const days = Math.floor(countDown / (1000 * 60 * 60 * 24)); const hours = Math.floor( (countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) ); const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((countDown % (1000 * 60)) / 1000); return [days, hours, minutes, seconds]; }; export { useCountdown };
2. 调整CountdownTimer组件的渲染逻辑
确保服务端和客户端的渲染分支一致,避免组件类型不匹配:
import React from 'react'; import { useCountdown } from '../hooks/useCountdown'; import DateTimeDisplay from './DateTimeDisplay'; const ExpiredNotice = () => { return ( <div className="expired-notice"> <span>It's time to CrissCross</span> </div> ); }; const ShowCounter = ({ days, hours, minutes, seconds }) => { return ( <div className="show-counter"> <span className='countdown-link'> <DateTimeDisplay value={days} type={'Days'} isDanger={true} /> <DateTimeDisplay value={hours} type={'Hours'} isDanger={false} /> <DateTimeDisplay value={minutes} type={'Mins'} isDanger={false} /> <DateTimeDisplay value={seconds} type={'Seconds'} isDanger={false} /> </span> </div> ); }; const CountdownTimer = () => { const [days, hours, minutes, seconds] = useCountdown(); const isClient = typeof window !== 'undefined'; // 服务端渲染时默认显示占位倒计时,客户端再判断是否过期 if (isClient && days + hours + minutes + seconds <= 0) { return <ExpiredNotice />; } else { return ( <ShowCounter days={days} hours={hours} minutes={minutes} seconds={seconds} /> ); } }; export default CountdownTimer;
关键修复点
- 避免在
useState初始值中使用非确定性值(如new Date()),这类值在服务端和客户端无法保证一致 - 利用
typeof window !== 'undefined'区分服务端/客户端环境,确保两边渲染的初始结构匹配 - 通过
useEffect在客户端挂载后再更新真实倒计时值,绕过服务端渲染的时间差异问题
内容的提问来源于stack exchange,提问作者Saarthak Chaturvedi
相关产品推荐
相关产品推荐

