Vitor
Vitor

Reputation: 11

Tanstack Table infinite table whenever the pagination or sorting changes

So I am trying to build a reusable Table component using Tanstack Table.

import { Box, Stack } from '@mui/material';
import type { SortingState, OnChangeFn, PaginationState } from '@tanstack/react-table';

import {
  useReactTable,
  getCoreRowModel,
  flexRender,
  getSortedRowModel,
  getPaginationRowModel,
} from '@tanstack/react-table';
import Text from '@components/Text';
import SortIconTable from '@components/SortIconTable';
import { useMemo, useState } from 'react';

export type PaginationShape = {
  cursor?: string | null | undefined;
  page: number;
  pageSize: number;
};

export type TableProps<TData> = {
  columns: TData[];
  cursor?: string | null | undefined;
  loading?: boolean;
  onPagination?: OnChangeFn<PaginationShape>;
  onSort?: OnChangeFn<SortingState>;
  pagination?: PaginationState;
  rows: TData[];
};

export type TablePropsColumns<TData> = TableProps<TData>['columns'];
const pageSizes = [10, 20, 50, 100, 500];

export const Table = ({ rows, columns, onSort, onPagination, cursor }: TableProps<any>) => {
  const [sorting, setSorting] = useState<SortingState>([]);
  const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: 10 });

  const memoizedRows = useMemo(() => rows, [rows]);
  const memoizedColumns = useMemo(() => columns, [columns]);

  const table = useReactTable({
    autoResetPageIndex: true,
    columnResizeMode: 'onChange',
    columns: memoizedColumns,
    data: memoizedRows,
    debugColumns: true,
    debugHeaders: true,
    debugTable: true,
    enableColumnFilters: true,
    enableColumnResizing: true,
    enableMultiSort: false,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
    manualPagination: true,
    manualSorting: true,
    onPaginationChange: async updater => {
      if (typeof updater !== 'function') return;

      const newPageInfo = await updater(table.getState().pagination);
      setPagination(() => newPageInfo);

      const formatPaginationData = {
        cursor,
        page: newPageInfo?.pageIndex,
        pageSize: newPageInfo?.pageSize,
      };

      onPagination?.(formatPaginationData);
    },
    onSortingChange: data => {
      setSorting(data);
      onSort?.(data);
    },
    sortDescFirst: false,
    state: {
      pagination,
      sorting,
    },
  });

  return (
    <div className="p-2">
      <Box sx={{ position: 'relative' }}>
        <table style={{ maxWidth: '100%' }}>
          <thead>
            {table.getHeaderGroups().map(headerGroup => (
              <tr key={headerGroup.id}>
                {headerGroup.headers.map(header => {
                  const canResize = header.column.getCanResize();
                  const canSort = header.column.getCanSort();
                  return (
                    <th colSpan={header.colSpan} key={header.id} style={{ width: header.getSize() }}>
                      <Stack direction={'row'}>
                        {flexRender(header.column.columnDef.header, header.getContext())}
                        {canSort && (
                          <SortIconTable
                            onSort={() => {
                              table.setSorting([
                                {
                                  desc: !header.column.getIsSorted(),
                                  id: header.column.id,
                                },
                              ]);
                              header.column.getToggleSortingHandler();
                            }}
                            sortDirection={header.column.getNextSortingOrder()}
                          />
                        )}
                        {canResize && (
                          <Box
                            className={`resizer ${header.column.getIsResizing() && 'isResizing'}`}
                            onDoubleClick={() => header.column.resetSize()}
                            onMouseDown={header.getResizeHandler()}
                            sx={{
                              '&.isResizing': {
                                background: 'blue',
                                cursor: 'col-resize',
                                opacity: 1,
                              },
                              '&:hover': {
                                cursor: 'col-resize',
                              },
                              border: '1px solid gray',
                              ml: 'auto',
                            }}
                          />
                        )}
                      </Stack>
                    </th>
                  );
                })}
              </tr>
            ))}
          </thead>
          <tbody>
            {table.getRowModel().rows.map(row => (
              <tr key={row.id}>
                {row.getVisibleCells().map(cell => (
                  <td key={cell.id} style={{ width: cell.column.getSize() }}>
                    <Text>{flexRender(cell.column.columnDef.cell, cell.getContext())}</Text>
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
          <div>
            <select
              onChange={e => {
                table.setPagination({
                  pageIndex: 0,
                  pageSize: Number(e.target.value),
                });
              }}
              value={table.getState().pagination.pageSize}
            >
              {pageSizes.map(pageSize => (
                <option key={pageSize} value={pageSize}>
                  {pageSize}
                </option>
              ))}
            </select>
          </div>
        </table>
      </Box>
    </div>
  );
};

and I am using as follow

const [sorting, setSorting] = useState<SortingState>([]);
  const [pagination, setPagination] = useState<PaginationShape>({ page: 0, pageSize: 10 });

  const params = {
    order_by: buildSortByString(sorting),
    page_size: pagination.pageSize,
  };

  console.log({ params });
  const { data } = useQuery<EventsLogResponse>({
    queryFn: () => getEventsLog({ params }),
    queryKey: ['getEventsLog', { params }],
  });

<CustomTable 
   columns={customColumns}
   cursor={data?.pagination_information.next_cursor}
   onPagination={setPagination}
   onSort={setSorting}
   rows={data?.events ?? []}
/>

I cant identify why, every time I update either the sorting or the pagination it starts an infinite loop. Any suggestions where the root cause is coming from?

I did try to clean up the state updates on both the Table component, or in the component calling the table, but the issue seems to be coming from within the table itself. I also thought it was a memo issue, and I added these two lines

const memoizedRows = useMemo(() => rows, [rows]);
  const memoizedColumns = useMemo(() => columns, [columns]);

but the problem still the same.

Upvotes: 1

Views: 532

Answers (0)

Related Questions