Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] useTable from @refinedev/react-table causes infinite rendering #6265

Open
khoaxuantu opened this issue Aug 18, 2024 · 9 comments
Open
Labels
bug Something isn't working good first issue Good for newcomers up for grabs

Comments

@khoaxuantu
Copy link

khoaxuantu commented Aug 18, 2024

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:

github-issue-reproduction

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.

image

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

├── [email protected]
├── [email protected]
├── [email protected]
├── @refinedev/[email protected]
├── @tanstack/[email protected]
├── @refinedev/[email protected]
├── @refinedev/[email protected]
├── @refinedev/[email protected]
├── @chakra-ui/[email protected]

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.

@khoaxuantu khoaxuantu added the bug Something isn't working label Aug 18, 2024
@BatuhanW
Copy link
Member

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.

@khoaxuantu
Copy link
Author

@BatuhanW Sure! It's quite complicated to reproduce minimally, but I just have created a branch of my project here to reproduce it. I have mocked the login and the data so that we can visit the /users page and look into the issue directly.
Would you mind checking it out?
Thanks

@aliemir
Copy link
Member

aliemir commented Aug 20, 2024

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 😅

@khoaxuantu
Copy link
Author

khoaxuantu commented Aug 20, 2024

@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

@aliemir
Copy link
Member

aliemir commented Aug 21, 2024

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 🙏

@khoaxuantu
Copy link
Author

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 🙇

@BatuhanW BatuhanW added the good first issue Good for newcomers label Aug 28, 2024
@Anonymous961
Copy link
Contributor

Hey @aliemir, were you suggesting something like this?

if (!isEqual(crudFilters, filtersCore)) {
      setFilters(crudFilters);
}

@aliemir
Copy link
Member

aliemir commented Sep 13, 2024

Hey @Anonymous961 yeah this is what I tried, would love to hear from you if you can spare some time to work on this 🙏

@chankruze
Copy link

Hey @aliemir, were you suggesting something like this?

if (!isEqual(crudFilters, filtersCore)) {
      setFilters(crudFilters);
}

Ye something like that!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working good first issue Good for newcomers up for grabs
Projects
None yet
Development

No branches or pull requests

5 participants