FAQ

如何停止无限渲染循环?

如果你正在使用 React,有一个非常常见的陷阱可能会导致无限渲染。 如果你未能为你的 columnsdatastate 提供稳定的引用,React 将在表格状态的任何更改时进入无限重新渲染的循环。

为什么会发生这种情况? 这是 TanStack Table 中的一个错误吗? 不是的。 这从根本上是 React 的工作方式,正确管理你的 columns、data 和 state 将防止这种情况发生。

TanStack Table 被设计为在传入表格的 datacolumns 发生更改时,或者在表格的任何 state 发生更改时,触发重新渲染。

未能为 columnsdata 提供稳定的引用可能会导致无限重新渲染循环。

陷阱 1:每次渲染时创建新列或数据

js
export default function MyComponent() {
  //😵 BAD: This will cause an infinite loop of re-renders because `columns` is redefined as a new array on every render!
  const columns = [
    // ...
  ];

  //😵 BAD: This will cause an infinite loop of re-renders because `data` is redefined as a new array on every render!
  const data = [
    // ...
  ];

  //❌ Columns and data are defined in the same scope as `useReactTable` without a stable reference, will cause infinite loop!
  const table = useReactTable({
    columns,
    data,
  });

  return <table>...</table>;
}
export default function MyComponent() {
  //😵 BAD: This will cause an infinite loop of re-renders because `columns` is redefined as a new array on every render!
  const columns = [
    // ...
  ];

  //😵 BAD: This will cause an infinite loop of re-renders because `data` is redefined as a new array on every render!
  const data = [
    // ...
  ];

  //❌ Columns and data are defined in the same scope as `useReactTable` without a stable reference, will cause infinite loop!
  const table = useReactTable({
    columns,
    data,
  });

  return <table>...</table>;
}

解决方案 1:使用 useMemo 或 useState 的稳定引用

在 React 中,你可以通过在组件外部/上方定义变量,或者通过使用 useMemouseState,或者通过使用第三方状态管理库(如 Redux 或 React Query 😉),为变量提供“稳定”的引用

js
//✅ OK: Define columns outside of the component
const columns = [
  // ...
];

//✅ OK: Define data outside of the component
const data = [
  // ...
];

// Usually it's more practical to define columns and data inside the component, so use `useMemo` or `useState` to give them stable references
export default function MyComponent() {
  //✅ GOOD: This will not cause an infinite loop of re-renders because `columns` is a stable reference
  const columns = useMemo(() => [
    // ...
  ], []);

  //✅ GOOD: This will not cause an infinite loop of re-renders because `data` is a stable reference
  const [data, setData] = useState(() => [
    // ...
  ]);

  // Columns and data are defined in a stable reference, will not cause infinite loop!
  const table = useReactTable({
    columns,
    data,
  });

  return <table>...</table>;
}
//✅ OK: Define columns outside of the component
const columns = [
  // ...
];

//✅ OK: Define data outside of the component
const data = [
  // ...
];

// Usually it's more practical to define columns and data inside the component, so use `useMemo` or `useState` to give them stable references
export default function MyComponent() {
  //✅ GOOD: This will not cause an infinite loop of re-renders because `columns` is a stable reference
  const columns = useMemo(() => [
    // ...
  ], []);

  //✅ GOOD: This will not cause an infinite loop of re-renders because `data` is a stable reference
  const [data, setData] = useState(() => [
    // ...
  ]);

  // Columns and data are defined in a stable reference, will not cause infinite loop!
  const table = useReactTable({
    columns,
    data,
  });

  return <table>...</table>;
}

陷阱 2:就地修改列或数据

即使你为你的初始 columnsdata 提供了稳定的引用,如果你就地修改它们,你仍然可能会遇到无限循环。 这是一个常见的陷阱,你可能最初没有注意到你在这样做。 像内联 data.filter() 这样简单的操作,如果你不小心,也可能导致无限循环。

js
export default function MyComponent() {
  //✅ GOOD
  const columns = useMemo(() => [
    // ...
  ], []);

  //✅ GOOD (React Query provides stable references to data automatically)
  const { data, isLoading } = useQuery({
    //...
  });

  const table = useReactTable({
    columns,
    //❌ BAD: This will cause an infinite loop of re-renders because `data` is mutated in place (destroys stable reference)
    data: data?.filter(d => d.isActive) ?? [],
  });

  return <table>...</table>;
}
export default function MyComponent() {
  //✅ GOOD
  const columns = useMemo(() => [
    // ...
  ], []);

  //✅ GOOD (React Query provides stable references to data automatically)
  const { data, isLoading } = useQuery({
    //...
  });

  const table = useReactTable({
    columns,
    //❌ BAD: This will cause an infinite loop of re-renders because `data` is mutated in place (destroys stable reference)
    data: data?.filter(d => d.isActive) ?? [],
  });

  return <table>...</table>;
}

解决方案 2:记忆化你的数据转换

为了防止无限循环,你应该始终记忆化你的数据转换。 这可以使用 useMemo 或类似方法来完成。

js
export default function MyComponent() {
  //✅ GOOD
  const columns = useMemo(() => [
    // ...
  ], []);

  //✅ GOOD
  const { data, isLoading } = useQuery({
    //...
  });

  //✅ GOOD: This will not cause an infinite loop of re-renders because `filteredData` is memoized
  const filteredData = useMemo(() => data?.filter(d => d.isActive) ?? [], [data]);

  const table = useReactTable({
    columns,
    data: filteredData, // stable reference!
  });

  return <table>...</table>;
}
export default function MyComponent() {
  //✅ GOOD
  const columns = useMemo(() => [
    // ...
  ], []);

  //✅ GOOD
  const { data, isLoading } = useQuery({
    //...
  });

  //✅ GOOD: This will not cause an infinite loop of re-renders because `filteredData` is memoized
  const filteredData = useMemo(() => data?.filter(d => d.isActive) ?? [], [data]);

  const table = useReactTable({
    columns,
    data: filteredData, // stable reference!
  });

  return <table>...</table>;
}

React Forget

当 React Forget 发布时,这些问题可能将成为过去。 或者只是使用 Solid.js... 🤓

当我的数据更改时,如何阻止我的表格状态自动重置?

大多数插件使用的 state 通常应该在数据源更改时重置,但有时你需要阻止这种情况发生,例如当你从外部筛选数据,或者在查看数据时以不可变的方式编辑数据,或者只是对数据进行任何外部操作,而你不想触发表格状态的某一部分自动重置。

对于这些情况,每个插件都提供了一种方法来禁用 state 在数据或 state 的其他依赖项更改时自动在内部重置。 通过将它们中的任何一个设置为 false,你可以阻止自动重置被触发。

这是一个基于 React 的示例,说明如何在编辑表格的数据源时,阻止基本上每个 state 的正常更改

js
const [data, setData] = React.useState([])
const skipPageResetRef = React.useRef()

const updateData = newData => {
  // When data gets updated with this function, set a flag
  // to disable all of the auto resetting
  skipPageResetRef.current = true

  setData(newData)
}

React.useEffect(() => {
  // After the table has updated, always remove the flag
  skipPageResetRef.current = false
})

useReactTable({
  ...
  autoResetPageIndex: !skipPageResetRef.current,
  autoResetExpanded: !skipPageResetRef.current,
})
const [data, setData] = React.useState([])
const skipPageResetRef = React.useRef()

const updateData = newData => {
  // When data gets updated with this function, set a flag
  // to disable all of the auto resetting
  skipPageResetRef.current = true

  setData(newData)
}

React.useEffect(() => {
  // After the table has updated, always remove the flag
  skipPageResetRef.current = false
})

useReactTable({
  ...
  autoResetPageIndex: !skipPageResetRef.current,
  autoResetExpanded: !skipPageResetRef.current,
})

现在,当我们更新我们的数据时,上面的表格状态将不会自动重置!

订阅 Bytes

您每周的 JavaScript 新闻。 每周一免费发送给超过 100,000 名开发者。

Bytes

无垃圾邮件。 随时取消订阅。