Next.js集成MUI受控日期选择器出现Hydration Error的原因
我正在使用Next.js与MUI构建表单输入组件,需实现受控日期选择器。采用如下代码方案时出现了Hydration Error(水合错误),其中StyledDatePickerInput是自定义样式的MUI日期选择器。为何日期选择器会触发该错误,而TextField等其他输入组件却无此问题?当移除代码中的{...field}和{...props}后,组件可正常渲染。
"use client"; import { StyledDatePickerInput } from "@/components/styled/StyledDatePicker/StyledDatePicker"; import { DatePickerProps } from "@mui/x-date-pickers"; import { Dayjs } from "dayjs"; import React, { FC } from "react"; import { Controller, useFormContext } from "react-hook-form"; interface FormDatePickerProps { name: string; } const FormDatePicker: FC<FormDatePickerProps & DatePickerProps<Dayjs>> = ({ name, ...props }) => { const { control } = useFormContext(); return ( <Controller control={control} name={name} render={({ field, fieldState: { error } }) => ( <StyledDatePickerInput {...field} {...props} /> )} /> ); }; export default FormDatePicker;
核心原因分析
Hydration Error本质是服务端渲染的HTML与客户端首次渲染的DOM结构/内容不匹配,日期选择器触发问题主要有这几个关键点:
field值类型不匹配
react-hook-form的field.value初始值可能为undefined,但MUI DatePicker(基于Dayjs)期望的是Dayjs实例或null。服务端渲染时,undefined会被渲染为空输入框;但客户端初始化时,DatePicker会尝试将undefined转换为Dayjs对象,导致DOM内容出现差异。而TextField对undefined的处理更宽松,直接渲染空值,不会触发类型转换后的DOM不一致。自定义组件的服务端兼容性问题
StyledDatePickerInput作为自定义组件,可能包含依赖浏览器环境的逻辑(比如日期格式化、DOM事件绑定),这些逻辑在服务端渲染时无法正常执行,导致服务端生成的HTML与客户端渲染结果不符。而TextField是MUI原生基础组件,服务端渲染适配更完善,不会出现这类问题。受控状态的冲突逻辑
react-hook-form的Controller会接管组件的受控状态,但DatePicker自身也有内部状态管理。当field中的onChange与DatePicker原生的onChange处理逻辑不一致时,客户端初始化阶段可能出现状态突变,引发DOM结构不匹配。TextField的受控逻辑更简单,与Controller的适配性更好,不会触发这类冲突。
修复方案
- 统一初始值类型:在Controller中明确设置
defaultValue为null,确保服务端与客户端的初始值类型一致:<Controller control={control} name={name} defaultValue={null} render={({ field, fieldState: { error } }) => ( <StyledDatePickerInput {...field} {...props} /> )} /> - 禁用服务端渲染:使用Next.js的
dynamic导入组件,强制仅在客户端渲染:import dynamic from 'next/dynamic'; const FormDatePicker = dynamic(() => import('./FormDatePicker'), { ssr: false }); - 检查自定义组件逻辑:确保
StyledDatePickerInput中依赖浏览器环境的代码(如日期格式化)仅在客户端执行,可通过useEffect或typeof window !== 'undefined'判断环境后再执行。
内容的提问来源于stack exchange,提问作者m_novak

