我最近开发了一个带有表格的组件,并决定是时候尝试一下
react-table
包了。
以下组件(我知道它很大,是一个用于测试目的的组件)导致无限调用
getRowModel
。我还添加了一个 useEffect
来跟踪与 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>
);
}
代码本质上是来自https://tanstack.com/table/v8/docs/examples/react/basic的示例代码。 当我删除
tbody
内的代码时,问题就停止了。
起初我认为这一定是我的自定义挂钩或
RouteGuard
组件的问题,但我检查了使用它们的其他组件中的重新渲染问题,并没有发现任何问题。
无论如何,我将在此处包含它们的代码:
使用FullFlows:
//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.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};
}
使用ActiveWorkspace
//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]);
}
使用工作区
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.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>
);
}
我尝试从方程中删除所有获取的数据并将静态数据传递到
useReactTable
钩子中,但这也不起作用。
唯一停止渲染的两件事是:
useFullFlows
钩子tbody
标签的内容我自己不知道可能导致问题的原因以及如何继续,主要考虑放弃
react-table
,但我想在此之前尝试获得一些帮助。
网上搜索也没有相关结果。
我希望我的组件仅在状态更改时重新渲染,而不是无明显原因地无限重新渲染。
我在我的桌子上使用
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]
);
尝试用备忘录包装你的组件,这对我很有帮助。