解决Fluent UI React <DetailList/>编辑值后UI不更新的问题
问题根源
- useEffect依赖错误:你把
editableItemId作为useEffect的依赖,导致每次双击切换编辑状态时,都会重新调用fetchData拉取原始数据,直接覆盖用户修改后的本地数据。 - Columns存储方式不当:将
columns存在state中且仅在editableItemId变化时更新,无法响应items的变化,可能导致渲染逻辑捕获旧状态。 - DetailsList的key设置不合理:使用
items.length作为key,当列表长度不变时,即使内容更新,React也不会触发组件重新渲染。
修复步骤
1. 修正useEffect依赖,仅初始化一次数据
把useEffect的依赖改为空数组,确保只在组件首次加载时拉取数据,避免覆盖用户修改:
useEffect(() => { const fetchData = async() => { const data = await fetchExampleData(); setItems(data); }; fetchData(); }, []); // 空数组表示仅组件挂载时执行
2. 优化Columns定义,用useMemo+useCallback管理
移除columns的state,改用useMemo结合useCallback确保渲染函数捕获最新状态:
// 用useCallback包裹渲染函数,依赖变化时自动更新 const renderEditableColumn = useCallback((item: ExampleDataItem) => ( editableItemId === item.id ? ( <input type="text" value={item.editedTitle ?? item.title} onChange={(e) => handlerInputChange(e, item.id)} onKeyDown={(e) => handleInputKeyDown(e, item.id)} /> ) : ( <span onDoubleClick={() => handleDoubleClick(item)}>{item.title}</span> ) ), [editableItemId]); // 假设renderCenteredColumn是你的其他渲染函数,同样用useCallback包裹 const renderCenteredColumn = useCallback((item: ExampleDataItem) => ( <div style={{ textAlign: 'center' }}>{item.userId}</div> ), []); // 用useMemo定义列配置,依赖渲染函数 const columns = useMemo(() => [ { key: 'id', name: 'User ID', fieldName: 'id', minWidth: 80, maxWidth: 120, isResizable: true }, { key: 'title', name: 'Case Title', fieldName: 'title', minWidth: 150, maxWidth: 250, isResizable: true, onRender: renderEditableColumn }, { key: 'userId', name: 'Case Number', fieldName: 'userId', minWidth: 80, maxWidth: 120, isResizable: true, onRender: renderCenteredColumn } ], [renderEditableColumn, renderCenteredColumn]);
3. 移除DetailsList上不合理的key
删除DetailsList组件的key={items.length}属性,让React根据props变化自动管理渲染:
<DetailsList items={items} columns={columns} setKey="set" ariaLabelForSelectionColumn="Toggle selection" ariaLabelForSelectAllCheckbox="Toggle selection for all items" checkButtonAriaLabel="select row" />
完整修复代码
const MyTable: React.FC = () => { const [items, setItems] = useState<ExampleDataItem[]>([]); const [editableItemId, setEditableItemId] = useState<number | null>(null); // 双击切换编辑状态 const handleDoubleClick = (item: ExampleDataItem) => { console.log('handleInputChange called, double click', item); setEditableItemId((prevId) => (prevId === item.id ? null : item.id)); }; // 可编辑列渲染函数 const renderEditableColumn = useCallback((item: ExampleDataItem) => ( editableItemId === item.id ? ( <input type="text" value={item.editedTitle ?? item.title} onChange={(e) => handlerInputChange(e, item.id)} onKeyDown={(e) => handleInputKeyDown(e, item.id)} /> ) : ( <span onDoubleClick={() => handleDoubleClick(item)}>{item.title}</span> ) ), [editableItemId]); // 输入框内容变化处理 const handlerInputChange = (e: React.ChangeEvent<HTMLInputElement>, itemId: number) => { console.log('handleInputChange called, value changing', e.target.value); const updateItems = items.map((item) => item.id === itemId ? { ...item, editedTitle: e.target.value } : item ); setItems(updateItems); } // 回车键确认编辑 const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, itemId: number) => { console.log('handleInputKeyDown called, new gg'); if (e.key === 'Enter') { setItems((prevItems) => { const updatedItems = prevItems.map((item) => item.id === itemId ? { ...item, title: item.editedTitle ?? item.title, editedTitle: undefined } : item ); console.log('Updated Items:', updatedItems); return updatedItems; }); setEditableItemId(null); } }; // 居中列渲染函数 const renderCenteredColumn = useCallback((item: ExampleDataItem) => ( <div style={{ textAlign: 'center' }}>{item.userId}</div> ), []); // 列配置 const columns = useMemo(() => [ { key: 'id', name: 'User ID', fieldName: 'id', minWidth: 80, maxWidth: 120, isResizable: true }, { key: 'title', name: 'Case Title', fieldName: 'title', minWidth: 150, maxWidth: 250, isResizable: true, onRender: renderEditableColumn }, { key: 'userId', name: 'Case Number', fieldName: 'userId', minWidth: 80, maxWidth: 120, isResizable: true, onRender: renderCenteredColumn } ], [renderEditableColumn, renderCenteredColumn]); // 初始化数据 useEffect(() => { const fetchData = async() => { const data = await fetchExampleData(); console.log(data.length); setItems(data); }; fetchData(); }, []); return ( <div className="my-table-container"> <DetailsList items={items} columns={columns} setKey="set" ariaLabelForSelectionColumn="Toggle selection" ariaLabelForSelectAllCheckbox="Toggle selection for all items" checkButtonAriaLabel="select row" /> </div> ); }; export default MyTable;
修复说明
- 修正useEffect依赖后,不会再在编辑状态变化时重新拉取数据,避免覆盖用户修改。
- 使用
useCallback和useMemo确保渲染函数和列配置能响应状态变化,让DetailsList正确感知数据更新。 - 移除不合理的key后,React能根据items和columns的变化自动触发组件重新渲染,同步显示更新后的值。
内容的提问来源于stack exchange,提问作者pifflox
相关产品推荐
相关产品推荐

