英文:
React-Table causing infinite re-renders
问题
It seems that your component is experiencing infinite re-renders, and you've isolated the issue to the tbody
section of your code. This could be related to how data is being processed and passed to react-table
.
I can provide some general guidance on debugging this issue:
-
Check Data Dependencies: Make sure that the data you are passing to
react-table
(in this case, theflows
data) is not changing constantly. Infinite re-renders can occur if the data source is being updated frequently. -
Memoization: Ensure that the data passed to
useReactTable
is properly memoized. In your code, it appears thatcolumns
andflows
are being memoized correctly usinguseMemo
. Double-check that other dependencies are also properly memoized. -
Console Logging: You mentioned that removing the contents of the
tbody
tag stops the issue. Try adding console logs in various parts of your code, especially in the rendering process, to see where the infinite re-renders are coming from. This might help pinpoint the problem. -
React DevTools: Use React DevTools to inspect component updates and identify which components are causing the excessive re-renders. It can provide valuable information about the component tree and render cycles.
-
React Profiler: If you have access to the React Profiler tool, it can help you visualize component render times and identify performance bottlenecks.
-
Library Version: Check if you are using the latest version of the
react-table
library. Sometimes, issues like this can be related to library bugs that are fixed in newer releases. -
External Dependencies: Examine the dependencies used within your components, including any custom hooks. Ensure that they are not causing unintentional re-renders.
-
Review React Table Configuration: Double-check the configuration of your
react-table
instance, including any callbacks or hooks you've provided. Incorrect usage of these configurations can lead to unexpected behavior. -
Optimize Rendering: If you find that a specific part of your component is causing excessive re-renders, consider optimizing it. You can use
React.memo
oruseCallback
to prevent unnecessary re-renders in child components. -
Test in Isolation: Try to isolate the problematic part of your code and create a minimal reproduction of the issue. This can help you focus on the specific code causing the problem.
Remember to approach this issue methodically, starting with data flow and dependencies. By systematically eliminating potential causes, you can pinpoint the source of the infinite re-renders and find a solution.
英文:
I recently worked on a component with a table, and decided it's time to give the react-table
package a try.
The following component (I know it is big, one component for testing purposes) is causing infinite calls to getRowModel
. I also included a useEffect
to follow re-renders unrelated to react-table
.
//flows.tsx
import { RouteGuard } from '@/components/utils/guards/RouteGuard';
import { useFullFlows } from '@/hooks/flows/useFullFlows';
import { Flow } from '@/shared/models/flows/flow';
import { Suspense, useEffect, useMemo, useState } from 'react';
import {
SortingState,
createColumnHelper,
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { FlowsPageHeader } from '../components/flows/view/FlowsPageHeader';
import moment from 'moment';
import Head from 'next/head';
const count = 50;
const columnHelper = createColumnHelper<Flow>();
export default function Workflows() {
const columns = useMemo(
() => [
columnHelper.accessor('name', {
header: 'Flow name',
sortingFn: 'alphanumeric',
}),
columnHelper.accessor('updatedAt', {
header: 'Modified',
cell: ({ cell: { getValue } }) =>
moment(getValue() as Date)
.local()
.format('DD.MM.YYYY'),
}),
columnHelper.display({
header: 'Preview',
cell: () => <Button size='sm' />,
}),
columnHelper.display({
header: 'Edit',
cell: () => <Button size='sm' />,
}),
columnHelper.display({
id: 'actions',
cell: () => (
<>
<Button></Button>
<Button></Button>
<Button></Button>
<Button></Button>
</>
),
}),
],
[]
);
const [page, setPage] = useState(0);
const { flows } = useFullFlows({ page, count: 50 });
useEffect(() => console.log('first'));
const { getHeaderGroups, getRowModel } = useReactTable<Flow>({
columns,
data: flows,
getCoreRowModel: getCoreRowModel(),
debugTable: process.env.NODE_ENV === 'development',
});
return (
<RouteGuard>
<Head>
<title>Flows</title>
</Head>
<FlowsPageHeader />
<div className='flex flex-col m-8 rounded-lg shadow-around'>
<div className='flex justify-between px-6 py-4'>
<div className='flex items-center gap-4'>
<Checkbox size='sm' className='mr-2 rounded-sm' />
<Dropdown>
<Dropdown.Toggle
size='xs'
className='[&>button]:rounded-sm'>
:
</Dropdown.Toggle>
</Dropdown>
<Input size='xs' className='rounded-sm' />
</div>
<div className='flex items-center gap-2'>
<Button size='xs' shape='square'>
{'<'}
</Button>
<Button size='xs' shape='square'>
{'>'}
</Button>
</div>
</div>
<table>
<thead>
{getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<div
{...{
className:
header.column.getCanSort()
? 'cursor-pointer select-none'
: '',
onClick:
header.column.getToggleSortingHandler(),
}}>
{flexRender(
header.column.columnDef
.header,
header.getContext()
)}
{{
asc: ' 🔼',
desc: ' 🔽',
}[
header.column.getIsSorted() as string
] ?? null}
</div>
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{getRowModel().rows.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</RouteGuard>
);
}
The code is essentially the sample code from https://tanstack.com/table/v8/docs/examples/react/basic.
When I remove the code inside tbody
, the problem stops.
At first I assumed it must be a problem with my custom hooks or the RouteGuard
componenet, but I checked for re-rendering issues in other componenets that use them, and found no issues.
Never the less, I will include the code for them here:
useFullFlows:
//useFullFlows.ts
import { useMemo } from 'react';
import { useFlows } from './useFlows';
import { getFlow } from '@/shared/api/flow';
import { useQueries } from '@tanstack/react-query';
import { Flow } from '@/shared/models/flows/flow';
export function useFullFlows({ page, count }: { page: number; count: number }) {
const { flows, ...query } = useFlows();
const fullFlows = useQueries({ queries: flows?.slice(page * count, (page + 1) * count).map(flow => ({
queryFn: () => getFlow(flow._id),
queryKey: ['flow', flow._id],
})) ?? [] });
return useMemo(() => ({
flows: fullFlows
.filter(query => query.data?.flow !== undefined)
.map(query => query.data?.flow as Flow),
...query
}), [fullFlows, query]);
}
useFlows
//useFlows.ts
import { getAllFlows } from '@/shared/api/flow';
import { useQuery } from '@tanstack/react-query';
import { useActiveWorkspace } from '../workspaces/useActiveWorkspace';
export function useFlows() {
const {workspace} = useActiveWorkspace()
const { data, ...query } = useQuery(['flows', workspace?._id], () =>
getAllFlows(workspace?._id ?? ''),
{enabled: workspace?._id ? true : false}
);
return {flows: data?.flows, ...query};
}
useActiveWorkspace
//useActiveWorkspace.ts
import { useUserStore } from '@/shared/store/userStore';
import { useEffect, useMemo } from 'react';
import { useWorkspaces } from './useWorkspaces';
export function useActiveWorkspace() {
const { workspaces, ...query } = useWorkspaces();
const {
activeWorkspace,
actions: { setActiveWorkspace },
} = useUserStore();
useEffect(() => {
if (activeWorkspace) return;
setActiveWorkspace(workspaces?.[0]._id ?? '');
}, [activeWorkspace, setActiveWorkspace, workspaces]);
return useMemo(() => {
return {
workspace: workspaces?.find(
workspace => workspace._id === activeWorkspace
),
...query,
};
}, [activeWorkspace, query, workspaces]);
}
useWorkspaces
import { useMemo } from 'react';
import { useUserInfo } from '../user/useUserInfo';
export function useWorkspaces() {
const { userInfo, ...query } = useUserInfo()
return useMemo(() => ({workspaces: userInfo?.workspaces, ...query}), [query, userInfo?.workspaces]);
}
RouteGuard
//RouteGuard.tsx
import { useUserStore } from '@/shared/store/userStore';
import { useRouter } from 'next/router';
import { PropsWithChildren, Suspense, useEffect, useState } from 'react';
import { Sidebar, SidebarOption } from '../../layout/Sidebar';
import { Loader } from '../Loader';
import { FullPageLoader } from '../FullPageLoader';
interface RouteGuardProps extends PropsWithChildren {
start?: SidebarOption[];
end?: SidebarOption[];
}
export function RouteGuard({ children, end, start }: RouteGuardProps) {
const router = useRouter();
const [authorized, setAuthorized] = useState(false);
const {
isAuthenticated,
actions: { setRedirectPath },
} = useUserStore();
useEffect(() => {
const authCheck = () => {
if (!isAuthenticated) {
setAuthorized(false);
setRedirectPath(router.asPath);
void router.push({
pathname: '/login',
});
} else {
setAuthorized(true);
}
};
authCheck();
const preventAccess = () => setAuthorized(false);
router.events.on('routeChangeStart', preventAccess);
router.events.on('routeChangeComplete', authCheck);
return () => {
router.events.off('routeChangeStart', preventAccess);
router.events.off('routeChangeComplete', authCheck);
};
}, [isAuthenticated, router, setRedirectPath]);
return authorized ? (
<div className='relative flex w-full h-full overflow-hidden'>
<Sidebar start={start} end={end} />
<Suspense fallback={<FullPageLoader />}>
<div className='w-full h-full overflow-auto'>{children}</div>
</Suspense>
</div>
) : (
<div className='flex items-center justify-center w-full h-full'>
<Loader color='primary' />
</div>
);
}
I tried removing all fetched data from the equation and passing static data into the useReactTable
hook but that also didn't work.
The only two things that stopped the renders were:
- Removing the
useFullFlows
hook from the code - Removing the contents of the
tbody
tag
I have no clue myself as to what might be causing the issue and how to proceed, and mainly thinking about ditching react-table
, but thought I'd try and get some help before that.
Searching online has yielded no relevant results too.
I excpect my component to only re-render upon state change, and not re-render infinitely for no apparent reason.
答案1
得分: 1
尝试使用 memo 包装你的组件,这对我有帮助。
英文:
Try wrapping your component with memo, it helped me.
答案2
得分: 0
以下是您要翻译的内容:
我在使用 useQuery
时遇到了相同的问题,与我的表格一起使用。问题是,当从 useQuery
中拉取数据时,我将数据设置为默认的 []
:
const {data = []} = useQuery( ... )
修复方法是在我记忆化数据的地方执行此逻辑:
const {data: theData} = useQuery( ... )
const data = React.useMemo(() => {
return Array.isArray(theData) ? theData : [];
}, [theFiles]);
const columns = React.useMemo<ColumnDef<File>[]>(
() => tableColumns.map(column => COLUMNS[column]),
[tableColumns]
);
英文:
I had the same issue using useQuery
with my table. The problem was that I was setting data to a default []
when pulling from useQuery
:
const {data = []} = useQuery( ... )
The fix was to do this logic inside the play where I was memoizing my data:
const {data: theData} = useQuery( ... )
const data = React.useMemo(() => {
return Array.isArray(theData) ? theData : [];
}, [theFiles]);
const columns = React.useMemo<ColumnDef<File>[]>(
() => tableColumns.map(column => COLUMNS[column]),
[tableColumns]
);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论