
Mantine 的 TextInput 组件在表格列头中失焦,根本原因是将函数组件(如 NameColumnHeader)错误地定义在父组件内部,导致每次渲染都创建新组件实例,破坏 React 的引用稳定性与焦点保持。
mantine 的 textinput 组件在每次按键后失焦,根本原因是将函数组件(如 namecolumnheader)错误地定义在父组件内部,导致每次渲染都创建新组件实例,破坏 react 的引用稳定性与焦点保持。
这个问题看似是 UI 行为异常,实则是 React 渲染机制与组件定义方式冲突的典型表现。
? 根本原因:组件内嵌定义引发重挂载
在你的 Customers 组件中,NameColumnHeader 被定义为一个内部函数组件:
const NameColumnHeader = () => ( /* ... */ );
React 视每个函数声明为独立组件。由于它位于 Customers 函数体内部,每次 Customers 重新渲染(例如 filter 状态更新、customers 变化等),都会生成一个全新的 NameColumnHeader 函数引用。而 DataTable.columns 是一个数组,当其中某个 title 属性指向一个新函数时,Mantine(或底层 React)会认为该列头组件已变更,从而卸载旧实例、挂载新实例 —— 导致 <textinput></textinput> 被销毁重建,自然失去焦点。
⚠️ 这不是 Mantine 的 Bug,而是 React 的设计原则:组件必须稳定引用,才能维持状态(包括输入框焦点、内部 form state、动画进度等)。
✅ 正确解法:提升组件至模块顶层
将 NameColumnHeader 提取为独立、稳定的函数组件,并确保其接收必要 props(如 filter 和 onFilterChange),避免闭包依赖不稳定变量:
// ✅ 正确:提升为顶层命名组件(可放同一文件顶部,或单独文件)
const NameColumnHeader: React.FC void;
}> = ({ filter, onFilterChange }) => (
<div classname="flex items-center justify-between">
<span classname="mr-2">Name</span>
<textinput placeholder="Search by Name..." value="{filter}" onchange="{(e)"> onFilterChange(e.currentTarget.value)}
rightSection={
filter && (
<actionicon size="sm" variant="transparent" c="dimmed" onclick="{()"> onFilterChange('')}
>
<iconx size="{14}"></iconx></actionicon>
)
}
/>
</textinput>
</div>
);
然后在 Customers 中使用它,并通过列配置传入状态和回调:
// 在 Customers 组件内:
const columns = [
{ accessor: 'client_id', title: '#' },
{
accessor: 'name',
title: (
<namecolumnheader filter="{filter}" onfilterchange="{setFilter}"></namecolumnheader>
),
},
{ accessor: 'code', title: 'Code' },
// ... 其他列
];
? 提示:
DataTable的columns数组应尽可能在useMemo中缓存(尤其含 JSX 的列),避免因无关状态变化导致列数组被重建:const columns = useMemo(() => [/* ... */], [filter]);
? 额外建议:优化数据获取与过滤逻辑
你当前的 useEffect 存在潜在问题:
useEffect(() => {
getCustomers()
.then((response) => setCustomers(response.data))
.then(() => setCustomersNotEmpty(true));
}, [customersNotEmpty]); // ❌ 错误依赖:会导致无限请求!
[customersNotEmpty] 作为依赖项,而 setCustomersNotEmpty(true) 又在 effect 内触发,将造成循环执行。应改为:
useEffect(() => {
let isMounted = true;
getCustomers()
.then((response) => {
if (isMounted) {
setCustomers(response.data);
setCustomersNotEmpty(response.data.length > 0);
}
})
.catch(console.error);
return () => { isMounted = false; };
}, []); // ✅ 仅在挂载时执行
同时,filteredData 建议用 useMemo 缓存,避免重复过滤:
const filteredData = useMemo(
() => customers?.filter((item: any) =>
item.name?.toLowerCase().includes(filter.toLowerCase())
) ?? [],
[customers, filter]
);
✅ 总结:三大关键实践
- ✅ 组件不嵌套:所有 JSX 组件(尤其是带状态的)必须定义在模块顶层,禁止在渲染函数内声明。
-
✅ 列配置缓存:
columns数组含函数组件时,务必useMemo包裹,依赖项精简。 -
✅ 副作用清理 + 合理依赖:数据请求 effect 应空依赖数组 +
isMounted检查,避免内存泄漏与无限循环。
遵循以上原则,你的搜索输入框将稳定聚焦,Mantine DataTable 的过滤功能即可开箱即用。










