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

使用Formik与MUI表格时的日期排序及防抖输入问题排查

问题排查与解决方案

一、日期排序异常问题

原因分析

升序转降序正常、转回升序失效的核心原因是直接对MM/DD/YYYY格式的日期字符串进行排序:

  • 字符串按字符顺序逐位对比,会出现日期逻辑与字符串排序结果不符的情况(比如"12/01/2023"字符串排序大于"01/02/2024",但实际日期后者更早);
  • 若初始排序用了日期解析,后续排序复用字符串比较,会导致排序逻辑不一致;
  • 排序时直接修改Formik的initialValues而非当前values,会导致排序基于旧数据,出现状态不同步。

解决方案

统一将日期字符串转换为时间戳(或Date对象)进行数值比较,确保排序逻辑前后一致:

  1. 编写日期解析函数,将MM/DD/YYYY转为可比较的时间戳:
const parseDateToTimestamp = (dateStr) => {
  const [month, day, year] = dateStr.split('/');
  // 注意月份是0-based,需减1
  return new Date(year, month - 1, day).getTime();
};
  1. 重构排序逻辑,基于Formik当前values而非初始值排序,升序/降序都用时间戳对比:
const handleSort = (column) => {
  const currentSort = sortConfig;
  const newDirection = 
    currentSort?.key === column && currentSort.direction === 'asc' 
      ? 'desc' 
      : 'asc';

  // 基于当前Formik的values进行排序,保证数据是最新的
  const sortedData = [...formik.values].sort((a, b) => {
    const timestampA = parseDateToTimestamp(a.invoice_date);
    const timestampB = parseDateToTimestamp(b.invoice_date);
    return newDirection === 'asc' ? timestampA - timestampB : timestampB - timestampA;
  });

  // 更新Formik值和排序状态
  formik.setValues(sortedData);
  setSortConfig({ key: column, direction: newDirection });
};
  1. 初始排序也使用上述parseDateToTimestamp方法,确保初始排序与后续排序逻辑统一。

二、invoice_notes防抖输入异常问题

原因分析

防抖导致文本消失、光标丢失、内容丢失的核心问题是将防抖逻辑绑定在表单值更新的环节:

  • 防抖延迟期间,用户输入的内容不会实时更新到Formik的values中,输入框显示值与实际输入不一致;
  • 防抖触发时更新Formik值,会重置输入框光标到末尾;
  • 快速输入时,防抖的异步特性会引发竞态条件:前一个防抖未执行,后一个已触发,最终只保留最后一次输入内容,中间输入被覆盖。

解决方案

将本地表单值更新与后端防抖保存分离,本地实时更新表单值,防抖仅用于触发后端请求:

  1. 输入框直接绑定Formik的实时更新方法,确保本地输入无延迟:
<TextField
  value={formik.values[index].invoice_notes}
  onChange={(e) => {
    // 实时更新Formik的字段值,不防抖
    formik.setFieldValue(`[${index}].invoice_notes`, e.target.value);
  }}
  fullWidth
  variant="outlined"
/>
  1. 单独用防抖处理后端保存请求,监听Formik值的变化触发:
import { useCallback, useEffect } from 'react';
import debounce from 'lodash.debounce';

// 定义防抖保存函数
const debouncedSaveInvoices = useCallback(
  debounce((values) => {
    // 调用后端API保存最新数据
    fetch('/api/invoices', {
      method: 'PUT',
      body: JSON.stringify(values),
      headers: { 'Content-Type': 'application/json' }
    });
  }, 500), // 可根据需求调整延迟时间
  []
);

// 监听Formik值变化,触发防抖保存
useEffect(() => {
  debouncedSaveInvoices(formik.values);
}, [formik.values, debouncedSaveInvoices]);

如果因特殊需求必须用防抖更新本地值(不推荐),可通过保存光标位置修复:

const handleNoteChange = (index) => (e) => {
  const input = e.target;
  const cursorPos = input.selectionStart;
  const newValue = e.target.value;
  
  debouncedUpdateNote(index, newValue, cursorPos);
};

const debouncedUpdateNote = useCallback(
  debounce((index, value, cursorPos) => {
    formik.setFieldValue(`[${index}].invoice_notes`, value);
    // DOM更新后恢复光标位置
    setTimeout(() => {
      const input = document.querySelector(`input[data-note-index="${index}"]`);
      if (input) {
        input.selectionStart = cursorPos;
        input.selectionEnd = cursorPos;
      }
    }, 0);
  }, 500),
  [formik]
);

// 输入框添加标识属性
<TextField
  data-note-index={index}
  value={formik.values[index].invoice_notes}
  onChange={handleNoteChange(index)}
  fullWidth
/>

内容的提问来源于stack exchange,提问作者Usman Khalid Mian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:10:20