React + Ant Design 日期选择器避免显示“Invalid Date“的解决方案

目录

项目场景:

当前例子是以React 18 + Ant Design + tsx


问题描述

在从API获取日期数据或用户输入日期时,当遇到无效日期字符串时,页面会显示"Invalid Date"错误:

API返回的日期字符串可能格式不正确

用户可能手动输入无效日期

组件没有对日期有效性进行验证

无效日期尝试格式化时出现错误

ts 复制代码
import React, { useState, useEffect } from 'react';
import { DatePicker, ConfigProvider, Button, message } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import dayjs from 'dayjs';
import 'antd/dist/reset.css';

// 模拟API接口
const mockApi = {
  // 模拟获取开通时间的API
  getActivationDate: (): Promise<string> => {
    return new Promise((resolve, reject) => {
      // 模拟网络延迟
      setTimeout(() => {
        // 随机返回有效或无效日期
        const random = Math.random();
        if (random < 0.3) {
          // 返回有效日期
          resolve('2023-10-15');
        } else if (random < 0.6) {
          // 返回无效日期格式
          resolve('2023-10-15T08:30:00Z'); // ISO格式,但dayjs可能解析错误
        } else {
          // 返回无效日期字符串
          resolve('无效日期');
        }
      }, 800);
    });
  },
  
  // 模拟保存开通时间的API
  saveActivationDate: (date: string): Promise<boolean> => {
    return new Promise((resolve) => {
      setTimeout(() => {
        // 检查日期是否有效
        const isValid = dayjs(date).isValid();
        resolve(isValid);
      }, 500);
    });
  }
};

// 有问题的日期选择器组件
const ProblematicDatePicker = () => {
  const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs | null>(null);
  const [apiDate, setApiDate] = useState<dayjs.Dayjs | null>(null);
  const [loading, setLoading] = useState(false);
  const [saving, setSaving] = useState(false);

  // 从API获取日期
  const fetchDateFromApi = async () => {
    setLoading(true);
    try {
      const dateString = await mockApi.getActivationDate();
      // 问题:直接转换API返回的字符串,没有验证
      const date = dayjs(dateString);
      setApiDate(date);
      message.info(`从API获取到日期: ${dateString}`);
    } catch (error) {
      message.error('获取日期失败');
    } finally {
      setLoading(false);
    }
  };

  // 保存日期到API
  const saveDateToApi = async () => {
    if (!selectedDate) {
      message.warning('请先选择日期');
      return;
    }
    
    setSaving(true);
    try {
      // 问题:直接使用dayjs对象,没有验证有效性
      const success = await mockApi.saveActivationDate(selectedDate.format());
      if (success) {
        message.success('日期保存成功');
      } else {
        message.error('保存失败:日期无效');
      }
    } catch (error) {
      message.error('保存日期失败');
    } finally {
      setSaving(false);
    }
  };

  // 有问题的日期格式化函数 - 直接转换,没有验证
  const formatDate = (date: dayjs.Dayjs | null) => {
    if (!date) return '无日期';
    
    // 问题:没有验证日期有效性
    return date.format('YYYY年MM月DD日');
  };

  // 组件加载时获取API日期
  useEffect(() => {
    fetchDateFromApi();
  }, []);

  return (
    <ConfigProvider locale={zhCN}>
      <div style={{ padding: '20px', maxWidth: '500px', margin: '0 auto', border: '1px solid #f0f0f0', borderRadius: '8px' }}>
        <h2 style={{ textAlign: 'center', color: '#ff4d4f' }}>有问题的日期选择器</h2>
        
        <div style={{ margin: '20px 0' }}>
          <div style={{ marginBottom: '8px', fontWeight: 'bold' }}>从API获取的开通时间:</div>
          <div style={{ 
            padding: '10px', 
            backgroundColor: '#f5f5f5',
            borderRadius: '4px',
            minHeight: '20px',
            color: apiDate && !apiDate.isValid() ? '#ff4d4f' : 'inherit'
          }}>
            {formatDate(apiDate)}
            {apiDate && !apiDate.isValid() && (
              <span style={{ color: '#ff4d4f', marginLeft: '10px' }}>⚠️ 无效日期</span>
            )}
          </div>
          <Button 
            type="primary" 
            onClick={fetchDateFromApi} 
            loading={loading}
            style={{ marginTop: '10px' }}
          >
            重新获取API日期
          </Button>
        </div>
        
        <div style={{ margin: '20px 0' }}>
          <div style={{ marginBottom: '8px', fontWeight: 'bold' }}>设置开通时间:</div>
          <DatePicker
            value={selectedDate}
            onChange={(date) => setSelectedDate(date)}
            placeholder="请选择日期"
            style={{ width: '100%' }}
            // 问题:没有处理无效日期输入
          />
          <div style={{ 
            marginTop: '10px', 
            padding: '10px', 
            backgroundColor: '#f5f5f5',
            borderRadius: '4px',
            minHeight: '20px',
            color: selectedDate && !selectedDate.isValid() ? '#ff4d4f' : 'inherit'
          }}>
            {formatDate(selectedDate)}
            {selectedDate && !selectedDate.isValid() && (
              <span style={{ color: '#ff4d4f', marginLeft: '10px' }}>⚠️ 无效日期</span>
            )}
          </div>
          <Button 
            type="primary" 
            onClick={saveDateToApi} 
            loading={saving}
            style={{ marginTop: '10px', width: '100%' }}
          >
            保存开通时间
          </Button>
        </div>
        
        <div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#fff2f0', borderRadius: '4px' }}>
          <h3>问题说明:</h3>
          <ul style={{ color: '#ff4d4f' }}>
            <li>API返回的日期字符串可能无效,但组件没有验证</li>
            <li>用户选择的日期没有有效性检查</li>
            <li>无效日期会显示"Invalid Date"或格式错误</li>
            <li>保存到API前没有验证日期有效性</li>
          </ul>
        </div>
      </div>
    </ConfigProvider>
  );
};

export default ProblematicDatePicker;

完整解决方案

typescript 复制代码
import React, { useState, useEffect } from 'react';
import { DatePicker, ConfigProvider, Button, message, Tag, Alert } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import dayjs from 'dayjs';
import 'antd/dist/reset.css';

// 修复点1:添加自定义日期解析函数
const safeDayjs = (dateString: string): dayjs.Dayjs | null => {
  try {
    const date = dayjs(dateString);
    return date.isValid() ? date : null;
  } catch (error) {
    return null;
  }
};

// 模拟API接口(保持不变)
const mockApi = {
  getActivationDate: (): Promise<string> => {
    return new Promise((resolve) => {
      setTimeout(() => {
        const random = Math.random();
        if (random < 0.3) {
          resolve('2023-10-15');
        } else if (random < 0.6) {
          resolve('2023-10-15T08:30:00Z');
        } else {
          resolve('无效日期');
        }
      }, 800);
    });
  },
  
  saveActivationDate: (date: string): Promise<boolean> => {
    return new Promise((resolve) => {
      setTimeout(() => {
        const isValid = dayjs(date).isValid();
        resolve(isValid);
      }, 500);
    });
  }
};

// 修复后的日期选择器组件
const FixedDatePicker = () => {
  const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs | null>(null);
  const [apiDate, setApiDate] = useState<dayjs.Dayjs | null>(null);
  const [loading, setLoading] = useState(false);
  const [saving, setSaving] = useState(false);
  const [apiError, setApiError] = useState<string | null>(null); // 修复点2:添加API错误状态

  // 修复点3:安全的日期格式化函数
  const formatDate = (date: dayjs.Dayjs | null) => {
    if (!date) return <Tag color="default">无日期</Tag>;
    
    // 修复点4:验证日期有效性
    if (!date.isValid()) {
      return <Tag color="error">无效日期</Tag>;
    }
    
    return <Tag color="blue">{date.format('YYYY年MM月DD日')}</Tag>;
  };

  // 修复点5:安全的日期获取函数
  const fetchDateFromApi = async () => {
    setLoading(true);
    setApiError(null);
    
    try {
      const dateString = await mockApi.getActivationDate();
      
      // 修复点6:使用安全的日期解析函数
      const date = safeDayjs(dateString);
      
      if (date) {
        setApiDate(date);
        message.success(`成功获取有效日期: ${date.format('YYYY-MM-DD')}`);
      } else {
        setApiDate(null);
        setApiError(`API返回无效日期: ${dateString}`);
        message.warning(`API返回无效日期: ${dateString}`);
      }
    } catch (error) {
      setApiError('获取日期失败');
      message.error('获取日期失败');
    } finally {
      setLoading(false);
    }
  };

  // 修复点7:安全的日期保存函数
  const saveDateToApi = async () => {
    if (!selectedDate) {
      message.warning('请先选择日期');
      return;
    }
    
    // 修复点8:保存前验证日期有效性
    if (!selectedDate.isValid()) {
      message.error('无法保存:选择的日期无效');
      return;
    }
    
    setSaving(true);
    try {
      const success = await mockApi.saveActivationDate(selectedDate.format());
      if (success) {
        message.success('日期保存成功');
      } else {
        message.error('保存失败:API验证日期无效');
      }
    } catch (error) {
      message.error('保存日期失败');
    } finally {
      setSaving(false);
    }
  };

  // 修复点9:处理日期选择变化
  const handleDateChange = (date: dayjs.Dayjs | null) => {
    if (date && !date.isValid()) {
      // 修复点10:对无效日期提供即时反馈
      message.warning('选择的日期无效,请重新选择');
      setSelectedDate(null);
      return;
    }
    setSelectedDate(date);
  };

  // 组件加载时获取API日期
  useEffect(() => {
    fetchDateFromApi();
  }, []);

  return (
    <ConfigProvider locale={zhCN}>
      <div style={{ 
        padding: '20px', 
        maxWidth: '500px', 
        margin: '0 auto', 
        border: '1px solid #e8e8e8', 
        borderRadius: '8px',
        boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
      }}>
        <h2 style={{ textAlign: 'center', color: '#52c41a' }}>修复后的日期选择器</h2>
        
        {/* 修复点11:添加API错误提示 */}
        {apiError && (
          <Alert 
            message={apiError} 
            type="warning" 
            showIcon 
            style={{ marginBottom: '20px' }}
          />
        )}
        
        <div style={{ margin: '20px 0' }}>
          <div style={{ marginBottom: '8px', fontWeight: 'bold' }}>从API获取的开通时间:</div>
          <div style={{ 
            padding: '10px', 
            backgroundColor: apiDate && apiDate.isValid() ? '#f6ffed' : '#fff2f0',
            border: `1px solid ${apiDate && apiDate.isValid() ? '#b7eb8f' : '#ffccc7'}`,
            borderRadius: '4px',
            minHeight: '20px',
          }}>
            {formatDate(apiDate)}
          </div>
          <Button 
            type="primary" 
            onClick={fetchDateFromApi} 
            loading={loading}
            style={{ marginTop: '10px' }}
          >
            重新获取API日期
          </Button>
        </div>
        
        <div style={{ margin: '20px 0' }}>
          <div style={{ marginBottom: '8px', fontWeight: 'bold' }}>设置开通时间:</div>
          <DatePicker
            value={selectedDate}
            onChange={handleDateChange} // 修复点12:使用安全的日期变更处理
            placeholder="请选择日期"
            style={{ width: '100%' }}
            // 修复点13:添加日期格式化和验证
            format="YYYY-MM-DD"
            disabledDate={(current) => {
              // 可选:禁用今天之后的日期
              return current && current > dayjs().endOf('day');
            }}
          />
          <div style={{ 
            marginTop: '10px', 
            padding: '10px', 
            backgroundColor: selectedDate && selectedDate.isValid() ? '#f6ffed' : '#fff2f0',
            border: `1px solid ${selectedDate && selectedDate.isValid() ? '#b7eb8f' : '#ffccc7'}`,
            borderRadius: '4px',
            minHeight: '20px',
          }}>
            {formatDate(selectedDate)}
          </div>
          <Button 
            type="primary" 
            onClick={saveDateToApi} 
            loading={saving}
            style={{ marginTop: '10px', width: '100%' }}
            // 修复点14:禁用无效日期的保存按钮
            disabled={!selectedDate || !selectedDate.isValid()}
          >
            保存开通时间
          </Button>
        </div>
        
        <div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#e6f7ff', borderRadius: '4px' }}>
          <h3>修复说明:</h3>
          <ul style={{ color: '#1890ff' }}>
            <li>添加了安全的日期解析函数 <code>safeDayjs</code></li>
            <li>所有日期操作前都验证有效性</li>
            <li>对无效日期提供友好的错误提示</li>
            <li>使用Ant Design的Tag组件增强视觉反馈</li>
            <li>添加了API错误状态处理</li>
            <li>保存按钮在日期无效时禁用</li>
          </ul>
        </div>
      </div>
    </ConfigProvider>
  );
};

export default FixedDatePicker;

原因分析:

直接解析未验证的日期字符串​​:

直接使用 dayjs(dateString)而没有验证日期有效性

​​缺乏错误处理​​:没有对无效日期提供友好的错误提示

​​缺少有效性检查​​:在保存到API前没有验证日期是否有效

​​用户体验差​​:无效日期显示原生错误信息,没有视觉反馈

​​缺乏安全解析​​:没有封装安全的日期解析函数

相关推荐
时雨__7 小时前
利用AndVX6开发流程图——问题总结
前端
云枫晖7 小时前
深入浅出npm:现代JavaScript项目基石
前端·javascript·node.js
文心快码BaiduComate7 小时前
Comate Zulu实测:不会编程也能做软件?AI程序员现状令人震惊
java·程序员·前端框架
不一样的少年_7 小时前
你家孩子又偷玩网页游戏? 试试这个防沉迷工具
前端·javascript·浏览器
春秋半夏7 小时前
vue2二次封装el-select支持collapse-tags-tooltip
前端
昔人'7 小时前
css`scrollbar-gutter`防止滚动条可见性变化时发生布局偏移
前端·css
掘金安东尼7 小时前
前端周刊第436期(2025年10月13日–10月19日)
前端·javascript·github
小叫花子7 小时前
用 UniApp 开发微信小程序蓝牙通信功能
前端