React Material UI出生日期输入组件的日期验证实现求助
实现React Material UI出生日期组件的日期验证功能
我开发了一个基于React Material UI的用户出生日期注册组件,拆分为日、月、年三个独立输入框。需求是:当用户输入无效日期(例如31-Feb-3000)并点击提交按钮时,在对应的日和年输入框显示红色错误边框,并展示“无效日期/无效年份”类的提示文字。以下是实现该功能的具体方案:
实现步骤
1. 添加错误状态管理
在组件中新增状态变量,跟踪日、年输入框的错误状态及是否触发过提交验证:
const [errors, setErrors] = useState({ day: '', year: '' }); const [isSubmitted, setIsSubmitted] = useState(false);
2. 编写日期验证函数
创建验证函数,检查年份有效性(范围、数值合理性)和日期有效性(避免Date自动修正无效日期):
const validateDate = () => { const newErrors = { day: '', year: '' }; const currentYear = new Date().getFullYear(); // 验证年份:非空、不早于1900、不晚于当前年份 if (!year || year < 1900 || year > currentYear) { newErrors.year = intl.formatMessage({ defaultMessage: '无效年份' }); } // 验证日期:确保构造的日期与输入值完全匹配,避免Date自动修正 if (day && month && year) { const testDate = new Date(year, month - 1, day); if ( testDate.getFullYear() !== year || testDate.getMonth() !== month - 1 || testDate.getDate() !== day ) { newErrors.day = intl.formatMessage({ defaultMessage: '无效日期' }); } } else if (day) { newErrors.day = intl.formatMessage({ defaultMessage: '请选择完整日期' }); } setErrors(newErrors); return !newErrors.day && !newErrors.year; };
3. 修改提交逻辑
在提交函数中先执行验证,验证通过后再调用接口更新数据:
const editDateOfBirth = () => { setIsSubmitted(true); const isValid = validateDate(); // 验证不通过则终止提交 if (!isValid) return; // 原有提交逻辑保持不变 let newYear; let newMonth; let newDate; const oldYear = dob?.getFullYear() || null; const oldMonth = dob?.getMonth() + 1 || null; const oldDate = dob?.getDate() || null; if (oldMonth !== month) { newMonth = month; } if (oldYear !== year) { newYear = year; } if (oldDate !== day) { newDate = day; } const oldDob = `${oldYear}-${oldMonth}-${oldDate}`; const newDob = `${newYear || oldYear}-${newMonth || oldMonth}-${ newDate || oldDate }`; const oldDobFormatted = !oldDob.includes('null') ? format(new Date(oldDob), 'yyyy-MM-dd') : null; const newDobFormatted = format(new Date(newDob), 'yyyy-MM-dd'); onEdit(oldDobFormatted, newDobFormatted); };
4. 更新输入框组件
给日、年输入框添加错误状态绑定,输入时自动清除对应错误提示:
// 日输入框 <TextField sx={{ width: 100 }} value={day} onChange={(e) => { setDay(Number(e.target.value)); if (isSubmitted) { setErrors(prev => ({ ...prev, day: '' })); } }} placeholder={intl.formatMessage({ defaultMessage: 'DD' })} label={intl.formatMessage({ defaultMessage: 'Day' })} inputProps={{ inputMode: 'numeric', pattern: '[0-9]*', min: 1, max: maxDay || 31, }} type="number" error={isSubmitted && !!errors.day} helperText={isSubmitted && errors.day} /> // 年输入框 <TextField sx={{ width: 100 }} value={year} onChange={(e) => { setYear(Number(e.target.value)); if (isSubmitted) { setErrors(prev => ({ ...prev, year: '' })); } }} placeholder={intl.formatMessage({ defaultMessage: 'YYYY' })} label={intl.formatMessage({ defaultMessage: 'Year' })} inputProps={{ inputMode: 'numeric', pattern: '[0-9]*', max: new Date().getFullYear(), }} type="number" error={isSubmitted && !!errors.year} helperText={isSubmitted && errors.year} />
5. 月份切换时清除日期错误
当月度变化时,之前的日期可能失效,自动清除日期错误提示:
<Select sx={{ width: 100 }} labelId="month-select" value={month} label={intl.formatMessage({ defaultMessage: 'Month' })} onChange={(e) => { setMonth(Number(e.target.value)); if (isSubmitted) { setErrors(prev => ({ ...prev, day: '' })); } }} > {months.map(m => ( <MenuItem key={m.monthString} value={m.monthNumber}> {m.monthString} </MenuItem> ))} </Select>
修改后的完整组件代码
import { format } from '@lib/date-util'; import { Box, FormControl, InputLabel, MenuItem, Select, Stack, TextField } from '@mui/material'; import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { calculateAge, formatOnlyDateToLocale, monthsList } from '../../utils/date-util'; import { EditableDetailTemplate } from './EditableDetailTemplate'; export const DateOfBirth = ({ dateOfBirth, onEdit }) => { const intl = useIntl(); const [editValue, setEditValue] = useState(dateOfBirth); const [errors, setErrors] = useState({ day: '', year: '' }); const [isSubmitted, setIsSubmitted] = useState(false); const dob = dateOfBirth ? new Date(dateOfBirth) : null; const [month, setMonth] = useState(dob?.getMonth() + 1); const [year, setYear] = useState(dob?.getFullYear()); const [day, setDay] = useState(dob?.getDate()); const fullDate = Boolean(year) && Boolean(month) ? new Date(`${year}-${month}-01`) : null; const maxDay = fullDate ? new Date(fullDate.getFullYear(), fullDate.getMonth() + 1, 0).getDate() : null; const validateDate = () => { const newErrors = { day: '', year: '' }; const currentYear = new Date().getFullYear(); // 验证年份 if (!year || year < 1900 || year > currentYear) { newErrors.year = intl.formatMessage({ defaultMessage: '无效年份' }); } // 验证日期(需确保构造的日期与输入一致,避免Date自动修正) if (day && month && year) { const testDate = new Date(year, month - 1, day); if ( testDate.getFullYear() !== year || testDate.getMonth() !== month - 1 || testDate.getDate() !== day ) { newErrors.day = intl.formatMessage({ defaultMessage: '无效日期' }); } } else if (day) { newErrors.day = intl.formatMessage({ defaultMessage: '请选择完整日期' }); } setErrors(newErrors); return !newErrors.day && !newErrors.year; }; const editDateOfBirth = () => { setIsSubmitted(true); const isValid = validateDate(); if (!isValid) return; let newYear; let newMonth; let newDate; const oldYear = dob?.getFullYear() || null; const oldMonth = dob?.getMonth() + 1 || null; const oldDate = dob?.getDate() || null; if (oldMonth !== month) { newMonth = month; } if (oldYear !== year) { newYear = year; } if (oldDate !== day) { newDate = day; } const oldDob = `${oldYear}-${oldMonth}-${oldDate}`; const newDob = `${newYear || oldYear}-${newMonth || oldMonth}-${ newDate || oldDate }`; const oldDobFormatted = !oldDob.includes('null') ? format(new Date(oldDob), 'yyyy-MM-dd') : null; const newDobFormatted = format(new Date(newDob), 'yyyy-MM-dd'); onEdit(oldDobFormatted, newDobFormatted); }; useEffect(() => { setEditValue(dateOfBirth); }, [dateOfBirth]); const months = monthsList(); return ( <EditableDetailTemplate onStartEditing={() => { setEditValue(editValue); // 进入编辑模式时重置错误状态 setIsSubmitted(false); setErrors({ day: '', year: '' }); }} onDoneEditing={() => { editDateOfBirth(month, year, day); }} displayComponent={ <Box sx={{ display: 'flex', alignItems: 'center' }}> <DobAndAge dateOfBirth={editValue} /> </Box> } editComponent={ <Stack direction="row" justifyContent="flex-start" alignItems="flex-start" spacing={3} > <TextField sx={{ width: 100 }} value={day} onChange={(e) => { setDay(Number(e.target.value)); if (isSubmitted) { setErrors(prev => ({ ...prev, day: '' })); } }} placeholder={intl.formatMessage({ defaultMessage: 'DD' })} label={intl.formatMessage({ defaultMessage: 'Day' })} inputProps={{ inputMode: 'numeric', pattern: '[0-9]*', min: 1, max: maxDay || 31, }} type="number" error={isSubmitted && !!errors.day} helperText={isSubmitted && errors.day} /> <FormControl> <InputLabel id="month-select">{intl.formatMessage({ defaultMessage: 'Month' })}</InputLabel> <Select sx={{ width: 100 }} labelId="month-select" value={month} label={intl.formatMessage({ defaultMessage: 'Month' })} onChange={(e) => { setMonth(Number(e.target.value)); if (isSubmitted) { setErrors(prev => ({ ...prev, day: '' })); } }} > {months.map(m => ( <MenuItem key={m.monthString} value={m.monthNumber}> {m.monthString} </MenuItem> ))} </Select> </FormControl> <TextField sx={{ width: 100 }} value={year} onChange={(e) => { setYear(Number(e.target.value)); if (isSubmitted) { setErrors(prev => ({ ...prev, year: '' })); } }} placeholder={intl.formatMessage({ defaultMessage: 'YYYY' })} label={intl.formatMessage({ defaultMessage: 'Year' })} inputProps={{ inputMode: 'numeric', pattern: '[0-9]*', max: new Date().getFullYear(), }} type="number" error={isSubmitted && !!errors.year} helperText={isSubmitted && errors.year} /> </Stack> } /> ); }; const DobAndAge = ({ dateOfBirth }) => dateOfBirth ? `${formatOnlyDateToLocale(dateOfBirth)} (age: ${calculateAge(dateOfBirth)})` : '';
内容的提问来源于stack exchange,提问作者Jakub
相关产品推荐
相关产品推荐

