[BUG] `useTable` from `@refinedev/react-table` causes infinite rendering
Opened this issue ยท 9 comments
Describe the bug
I was trying to create a list view following up the example of useTable
. However, when I got to the page, the page was rendering infinitely as the following:
It seems like the tableQuery
's status remains its fetchStatus as "fetching", but there is no calling to the data provider's getList
client action, and the data remains undefined
, so no data can be displayed.
Steps To Reproduce
My code is mostly the same with the usage example. The difference is it is built on top of Next.js's app routing.
Here is my Refine's layout:
<Refine
routerProvider={routerProvider}
dataProvider={DataProviderClient}
authProvider={authProvider}
notificationProvider={useNotificationProvider}
resources={[
{
name: "users",
list: "users",
create: "users/create",
edit: "users/:id/edit",
show: "users/:id",
},
]}
options={{
syncWithLocation: true,
warnWhenUnsavedChanges: true,
useNewQueryKeys: true,
}}
>
{children}
</Refine>
Here is my page at /src/app/users/page.tsx
"use client";
import { HStack, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from "@chakra-ui/react";
import { ColumnFilter } from "@components/DataDisplay/ColumnFilter";
import { Pagination } from "@components/Pagination";
import { capitalize } from "@lib/helpers/string.helper";
import { DateField, EditButton, List, ShowButton, TagField, TextField } from "@refinedev/chakra-ui";
import { useTable } from "@refinedev/react-table";
import { ColumnDef, flexRender } from "@tanstack/react-table";
import { useMemo } from "react";
import { UserProps } from "./schema/user.schema";
const LIST_PROPS_AS_TEXT = ["name", "email"];
export default function UsersPage() {
const columns = useMemo<ColumnDef<UserProps>[]>(
() => [
{
id: "_id",
header: "ID",
accessorKey: "_id",
meta: {
filterOperator: "eq",
},
},
...LIST_PROPS_AS_TEXT.map((field) => ({
id: field,
header: capitalize(field),
accessorKey: field,
})),
{
id: "roles",
header: "Roles",
accessorKey: "roles",
cell: (props) => {
return (
<HStack>
{(props.getValue() as string[]).map((role: string, index) => (
<TagField key={index} value={role} />
))}
</HStack>
);
},
},
{
id: "createdAt",
header: "Created At",
accessorKey: "createdAt",
cell: (props) => <DateField value={props.getValue() as string} format="HH:mm DD/MM/YYYY" />,
},
{
id: "actions",
header: "Actions",
accessorKey: "_id",
cell: (props) => {
return (
<HStack>
<EditButton recordItemId={props.getValue() as string} />
<ShowButton recordItemId={props.getValue() as string} />
</HStack>
);
},
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
refineCore: { tableQuery, pageCount, current, setCurrent },
} = useTable<UserProps>({ columns });
console.log("๐ ~ UsersPage ~ Render", tableQuery);
const total = tableQuery?.data?.total ?? 0;
return (
<List>
<p>Total: {total}</p>
<TableContainer marginTop={12}>
<Table variant="simple">
<Thead>
{getHeaderGroups().map((headerGroup) => {
return (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<Th key={header.id}>
{!header.isPlaceholder && (
<HStack spacing={2}>
<TextField
value={flexRender(
header.column.columnDef.header,
header.getContext(),
)}
/>
<ColumnFilter<UserProps> column={header.column} />
</HStack>
)}
</Th>
);
})}
</Tr>
);
})}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => {
return (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<Td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Td>
);
})}
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
<Pagination current={current} pageCount={pageCount} setCurrent={setCurrent} />
</List>
);
}
Expected behavior
- I expect to have the data rendered when visiting the page.
Packages
โโโ next@14.2.5
โโโ react@18.3.1
โโโ react-dom@18.3.1
โโโ @refinedev/react-table@5.6.13
โโโ @tanstack/react-table@8.20.1
โโโ @refinedev/core@4.54.0
โโโ @refinedev/nextjs-router@6.1.0
โโโ @refinedev/chakra-ui@2.32.0
โโโ @chakra-ui/react@2.8.2
Additional Context
Btw, I saw in the source code that it may pass an empty array []
to the TanStack's reactTable
here.
Wondering if it has any effect on the infinite rendering of useTable
. Because there was a TanStack's issue that is quite similar to it.
Hey @khoaxuantu, I couldn't reproduce the issue from the code you've provided. Could you provide a repository with a minimal repro? That would be great! Thanks.
Just checked out the codebase you've provided @khoaxuantu. I was able to reproduce the same rendering issue with the initial setup. Then it resolved when I removed the onClick
prop of the prev button in the <Pagination />
component. Can you check if this works? Instead of using current - 1
, I've updated it to pagination.prev
, not sure if this is something related with the caches but it started working on every test I make ๐
@aliemir It doesn't work 100% correct, sadly. I have tried both current - 1
and pagination.nev
in the <Pagination />
component as you said. The issue in both occurred as follows:
- From the root path
/
, I navigated to the/users
(via navigation sidebar), I got the issue. - Then I clicked on the pagination buttons, and it works. It did call the
getList
action, fetch the data successfully, and stop rendering infinitely. - Then I navigated to
/
, then reloaded the whole site, and clicked on the Users navigation sidebar again, the issue occurred again
Another thing that I noticed is that if I visit the /users
path by entering the url directly, the page is renderred successfully.
So I don't think the problem is from the pagination component
Hey @khoaxuantu you are right! My bad, I only tested out in the same page and hot reloading fixed the issue when I made changes in the <Pagination />
๐คฆ I investigated a bit more into the issue, it looks like its only related to the useTable
of @refinedev/react-table
, when I switch to using useTable
from @refinedev/core
the issue did not occur.
I've found that the issue happens when syncing sorters and filters from React Table to Refine. We did not had a check if the current states are equal or not and left with repeated calls to setFilters
. This caused queries to stuck at loading without properly calling the API. As a workaround I saw that setting syncWithLocation
to false
resolves the issue. For a fix, we need to add equality checks for filters and sorters in their effects.
When the issue is stuck at loading, Refine's overtime interval keeps running, this was causing your console.log to run repeatedly every second. It looks like an infinite rendering issue but its really logging due to overtime.elapsedTime
getting updated ๐
In my local, I've tested out using lodash/isEqual
before calling setFilters
in a useEffect
of useTable
and it seems to resolve the issue ๐ค
Let us know if syncWithLocation: false
workaround is working for you, then we can discuss about the fix. Also let us know if you want to work on the fix, we'll be happy to see your contribution ๐
Hey @aliemir, I just tried setting syncWithLocation
to false
and it worked as expected. I think I will go with this workaround for now.
Because of my limited time, I think it's better to let you fix the issue and I hope to be able to turn on the option in the future releases soon.
Thank you very much for the support ๐
Hey @aliemir, were you suggesting something like this?
if (!isEqual(crudFilters, filtersCore)) {
setFilters(crudFilters);
}
Hey @Anonymous961 yeah this is what I tried, would love to hear from you if you can spare some time to work on this ๐