You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现倒计时功能时React Web应用出现Hydration Error求助

解决React倒计时组件的Hydration Error问题

错误原因

Hydration Error的核心是服务端渲染(SSR)生成的HTML与客户端首次渲染的DOM内容/结构不匹配。你的代码里存在两个关键问题:

  1. useCountdown的初始countDown值直接通过new Date().getTime()计算,服务端和客户端的系统时间、渲染时刻存在差异,导致两边生成的倒计时数值完全不同
  2. 若服务端判断倒计时已过期(渲染<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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 11:35:11