table icon indicating copy to clipboard operation
table copied to clipboard

Table keeps re-rendering

Open DoronTorangy opened this issue 2 years ago • 12 comments

Describe the bug

I created table and it just keeps re-rendering, not really sure why

Your minimal, reproducible example

https://codesandbox.io/s/mantine-playground-forked-pslviy?file=/src/App.tsx:0-3415

Steps to reproduce

  1. Run the codesandbox
  2. Click on one of the table headers
  3. The component keeps re-rendering

Expected behavior

Table shouldn't be re-rendered unless content/data was changed

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

macOS, happens in both safari and chrome

react-table version

v8.7.4

TypeScript version

v4.9.3

Additional context

Found out that if the data property that I pass to useReactTable is wrapped in useMemo the problem resolves, but why?

const table = useReactTable({
		data: useMemo(() => [], []), // stops the problem from happening
                ...
})

also if you comment out the <tbody> jsx the problem stops

Terms & Code of Conduct

  • [x] I agree to follow this project's Code of Conduct
  • [X] I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

DoronTorangy avatar Dec 20 '22 06:12 DoronTorangy

It looks like that the data property in useReactTable has to have the same reference across renders, otherwise @tanstack/react-table keeps re-rendering the component...

The problem is that my data is passed from react-query useQuery, so I don't really have a solution

DoronTorangy avatar Dec 21 '22 17:12 DoronTorangy

Anyone has any idea what can be the problem here?

DoronTorangy avatar Dec 22 '22 18:12 DoronTorangy

I also noticed this is my app. This only happens when i use Nextjs for example. Also im just passing a empty array for data and column

Here is a codesandbox with the problem: codesandbox with nextjs

Here is a codesandbox without nextjs and you see that the render issue does not occur: codesandbox without nextjs

julianklumpers avatar Dec 23 '22 19:12 julianklumpers

I'm also facing the same issue.

My data is passed from useSWR and it just keeps rendering while my data is undefined.

Edit: I solved mine by using useMemo and returning an empty array if the data is still undefined.

mapringg avatar Dec 24 '22 05:12 mapringg

Stuck with the same issue

egorguscha avatar Mar 30 '23 13:03 egorguscha

Guys, you pass new instance of data with each render. data has to be stable. If you need empty array, at least declare it next to the component const emptyArray = []; and use that instead of inlined []. Because [] !== [], but emptyArray === emptyArray

Quoted from original answer

hichemfantar avatar Apr 05 '23 20:04 hichemfantar

Anyone who runs into this issue: It's because your data does not have a stable reference. Memoize it or store it in state.

KevinVandy avatar Apr 06 '23 13:04 KevinVandy

I ran into this because I was filtering my data inside my functional react component, which caused the list to change, causing the table to change, causing a re-render. Memoizing did not work for me, I had to move the filter logic outside of my component that contained the react table.

ice1080 avatar May 15 '23 05:05 ice1080

I am facing the same issue in RedwoodJS

Edit: Solved using useMemo on both columns and data variable

Mark0047 avatar Jun 03 '23 17:06 Mark0047

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

const columns = React.useMemo<ColumnDef<File>[]>(
  () => tableColumns.map(column => COLUMNS[column]),
  [tableColumns]
);

megancooper avatar Jul 18 '23 03:07 megancooper

In my case the problem was caused not by data but by columns. The DataTable had inputs in its cells which had a draft state that I was tracking at the level of the component that was rendering the DataTable. On each change of the input the draft state would update and that would update the columns (such that the new cells would contain new inputs with the correct draft value. This works fine in a straight HTML table assuming your keys are set up properly, but in DataTable the columns seem to need to be referentially equal or the whole table re-renders completely (not just reconciles the VDOM changes to the DOM) and the input that is being edited loses focus (because it is replaced by a new input with new text).

I solved this by moving the draft state into its own component used at the cell level. The component is returned by cell in columns and takes the table row's model but encapsulates the draft state completely within itself. When the editing is done (on input blur or when the Enter key is pressed) I go and update the backing data source which invalidates data and re-renders the whole table clearing the inputs draft state because again at that point the input is replaced by a completely new DOM element. This event then also causes any row-level state affected by the cell-level state to become in sync again.

I don't know how I would have solved this is the content of one cell (like a validity icon) depended on the content of another (like a text field). There is no way that I can see to keep row-level live state without re-rendering the whole table each time. In my case fortunately, the cell-level state tracking while editing and table-level state update when finished editing the cell were all I needed.

Hopefully this helps someone else.

TomasHubelbauer avatar Aug 20 '23 14:08 TomasHubelbauer

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

const columns = React.useMemo<ColumnDef<File>[]>(
  () => tableColumns.map(column => COLUMNS[column]),
  [tableColumns]
);

Wrapping useMemo() to those props does help. Thank you for your inspiration. @megancooper

For my case, I have a schema applied to each cell with an input. If a validation fails for an input then the cell will rerender with an error message. This rerender will 'blur' my input there.

My guess is that, by wrapping useMemo() to those props,it will only rerender part of the component inside the cell instead of the whole cell.

nathanwkwong avatar Oct 11 '23 15:10 nathanwkwong

image

KevinVandy avatar Mar 21 '24 16:03 KevinVandy

add these on your useReactTable

autoResetPageIndex: false, autoResetExpanded: false,

dhrin1 avatar Mar 22 '24 01:03 dhrin1

add these on your useReactTable

autoResetPageIndex: false, autoResetExpanded: false,

Those options should have nothing to do with infinite re-renders. Failing to give columns and data a stable reference will though.

KevinVandy avatar Mar 22 '24 01:03 KevinVandy

add these on your useReactTable

autoResetPageIndex: false, autoResetExpanded: false,

it work for me

TejasThombare20 avatar May 18 '24 12:05 TejasThombare20

In my case the problem was caused not by data but by columns. The DataTable had inputs in its cells which had a draft state that I was tracking at the level of the component that was rendering the DataTable. On each change of the input the draft state would update and that would update the columns (such that the new cells would contain new inputs with the correct draft value. This works fine in a straight HTML table assuming your keys are set up properly, but in DataTable the columns seem to need to be referentially equal or the whole table re-renders completely (not just reconciles the VDOM changes to the DOM) and the input that is being edited loses focus (because it is replaced by a new input with new text).

I solved this by moving the draft state into its own component used at the cell level. The component is returned by cell in columns and takes the table row's model but encapsulates the draft state completely within itself. When the editing is done (on input blur or when the Enter key is pressed) I go and update the backing data source which invalidates data and re-renders the whole table clearing the inputs draft state because again at that point the input is replaced by a completely new DOM element. This event then also causes any row-level state affected by the cell-level state to become in sync again.

I don't know how I would have solved this is the content of one cell (like a validity icon) depended on the content of another (like a text field). There is no way that I can see to keep row-level live state without re-rendering the whole table each time. In my case fortunately, the cell-level state tracking while editing and table-level state update when finished editing the cell were all I needed.

Hopefully this helps someone else.

this is very similar to what I'm facing right now. My table cells have input cells, datePickers e.t.c that when updated, call an onChange function that updates the database and invalidates a query key, which changes the state and I assume causes the whole table to rerender

Could you share some more specific info on this? maybe some code perhaps

adeleke5140 avatar Jun 28 '24 17:06 adeleke5140

Could you share some more specific info on this? maybe some code perhaps

+1

YoSoyPhil avatar Jul 17 '24 07:07 YoSoyPhil

I'm not sure if it's the same issue, but I solved the excessive re-rendering issue that occurred when using @tanstack/react-table by using @tanstack/react-virtual

NuclearPunch avatar Jul 24 '24 06:07 NuclearPunch