使用Formik与MUI表格时的日期排序及防抖输入问题排查
问题排查与解决方案
一、日期排序异常问题
原因分析
升序转降序正常、转回升序失效的核心原因是直接对MM/DD/YYYY格式的日期字符串进行排序:
- 字符串按字符顺序逐位对比,会出现日期逻辑与字符串排序结果不符的情况(比如
"12/01/2023"字符串排序大于"01/02/2024",但实际日期后者更早); - 若初始排序用了日期解析,后续排序复用字符串比较,会导致排序逻辑不一致;
- 排序时直接修改Formik的
initialValues而非当前values,会导致排序基于旧数据,出现状态不同步。
解决方案
统一将日期字符串转换为时间戳(或Date对象)进行数值比较,确保排序逻辑前后一致:
- 编写日期解析函数,将
MM/DD/YYYY转为可比较的时间戳:
const parseDateToTimestamp = (dateStr) => { const [month, day, year] = dateStr.split('/'); // 注意月份是0-based,需减1 return new Date(year, month - 1, day).getTime(); };
- 重构排序逻辑,基于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 }); };
- 初始排序也使用上述
parseDateToTimestamp方法,确保初始排序与后续排序逻辑统一。
二、invoice_notes防抖输入异常问题
原因分析
防抖导致文本消失、光标丢失、内容丢失的核心问题是将防抖逻辑绑定在表单值更新的环节:
- 防抖延迟期间,用户输入的内容不会实时更新到Formik的
values中,输入框显示值与实际输入不一致; - 防抖触发时更新Formik值,会重置输入框光标到末尾;
- 快速输入时,防抖的异步特性会引发竞态条件:前一个防抖未执行,后一个已触发,最终只保留最后一次输入内容,中间输入被覆盖。
解决方案
将本地表单值更新与后端防抖保存分离,本地实时更新表单值,防抖仅用于触发后端请求:
- 输入框直接绑定Formik的实时更新方法,确保本地输入无延迟:
<TextField value={formik.values[index].invoice_notes} onChange={(e) => { // 实时更新Formik的字段值,不防抖 formik.setFieldValue(`[${index}].invoice_notes`, e.target.value); }} fullWidth variant="outlined" />
- 单独用防抖处理后端保存请求,监听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
相关产品推荐
相关产品推荐

