React-Table 导致无限重新渲染

huangapple go评论63阅读模式
英文:

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:

  1. Check Data Dependencies: Make sure that the data you are passing to react-table (in this case, the flows data) is not changing constantly. Infinite re-renders can occur if the data source is being updated frequently.

  2. Memoization: Ensure that the data passed to useReactTable is properly memoized. In your code, it appears that columns and flows are being memoized correctly using useMemo. Double-check that other dependencies are also properly memoized.

  3. 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.

  4. 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.

  5. React Profiler: If you have access to the React Profiler tool, it can help you visualize component render times and identify performance bottlenecks.

  6. 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.

  7. External Dependencies: Examine the dependencies used within your components, including any custom hooks. Ensure that they are not causing unintentional re-renders.

  8. 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.

  9. 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 or useCallback to prevent unnecessary re-renders in child components.

  10. 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(() =&gt; {
  return Array.isArray(theData) ? theData : [];
}, [theFiles]);

const columns = React.useMemo&lt;ColumnDef&lt;File&gt;[]&gt;(
  () =&gt; tableColumns.map(column =&gt; COLUMNS[column]),
  [tableColumns]
);

huangapple
  • 本文由 发表于 2023年5月26日 07:31:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76336805.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定