Repository navigation
[v9] Data update from external source doesn't trigger rerenders in components with table object references #6601
Description
TanStack Table version
v9.2.4
Framework/Library version
React 19.3.0
Describe the bug and the steps to reproduce it
The documentation mentions that in v9 the data is an atom which is not reactive from my understanding. A common scenario in many applications is extracting components for header, body, footer, even the whole table to have a single component to style and manage.
Especially with React Compiler enabled, I noticed that many reusable components via createTableHook were not rerendering, although I could see state updates in the devtools. The documentation explains this behaviour here, suggesting the following:
If a component depends on several state slices, subscribe to table.store and return all of those slices from the selector. Keep the state-dependent Table calls inside the Subscribe render function:
The selector must include every state slice that can change the result of table.getRowModel() for this table. That may include filtering, sorting, grouping, expansion, or pagination, depending on the features in use.
While I can use table.Subscribe to select the table feature states, I haven't found a way to subscribe to data changes other than to pass the whole data with prop drilling to sync data changes with consumer components (I still won't use the data, I will use row models, but at least this ensures row model consumers will be rerendered when data changes). There is an example that shows a reusable TableBody component, but this has the same issue: if data changes and table subscribed states are not the reason why (see scenarios below), the component will not rerender and stale data will be shown until a pagination, filter, sorting or any other subscribed feature state is updated.
There are several valid scenarios where this may be needed, cases where data may be externally updated and the table needs to rerender to show the latest state, such as:
- if
datais a React state, calling setState - using Tanstack Query, there is
polling,refetchOnWindowFocusor infinite query scroll event listeners - Websocket updates
In the sandbox you will find 2 tables and the realtime data from the state which I update every 2 seconds:
-
table1is based on a datatable component extracted viacreateTableHook, similar to v8 style reusable table components. While the documentation says that the changes indatawill cause the row models to be regenerated, thetableobject itself is a stable reference which has not changed (feature states have not changed), so althoughtable.getRowModel()returns different, new data, thetableobject doesn't trigger a rerender to reflect that since no subscribed state has been updated. -
table2is comes from an example from the documetation where the table reusable components are consumed in the same component where thedataandtablelive (Appin this case). While thetablerows update, I think this example is misleading in the documentation as the nested components are updated because theAppcomponent rerenders due tosetDatarather than the Tanstack Table internal signals. If I were to have a new component in between, soAppconsumesNewComponentwhich consumesDatatableBody, the table rows will not be rerendered just like in table1.
This behaviour theoretically could apply to any table option change as well (because they are not states), I don't know if this may be an issue or not for some users, just stating what I found. Also, while this behaviour is more evident in apps with React Compiler enabled due to a potential case of internal mutability, the same behaviour should happen without the compiler as well with manual memoization. The only difference is that without the compiler, by default if the data changes, it will be in the same or a higher component than the table components, so every component below will be rerendered, masking this issue.
I hope it's a valid issue and not me not understanding how to new state management works under the hood, wasting your time :(
What is the best way to handle this scenario where I want to have table data that may be updated externally?
What is the correct way to defined reusable components via createTableHook that are reactive to the row model changes? Are there any examples?
PS: Fantastic job on the v9 update btw, apart from this, I have been really enjoying it and the codebase looks cleaner than ever.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
https://stackblitz.com/edit/github-eyxvcbv9?file=src%2Fmain.tsx
Screenshots or Videos (Optional)
No response
Do you intend to try to help solve this bug with your own PR?
None
Terms & Code of Conduct
- I agree to follow this project's Code of Conduct
- 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.
Activity
tevinch commentedon Oct 3, 2026
The first table in the sandbox stays on its initial timestamp while the source data and second table advance. There are two dependencies to connect here: the externally replaced data, and the feature state used by the row model.
For an application using 9.2.4, a data Context can carry the first dependency without forwarding unused props through intermediate components. Give the provider the same data reference passed to
useAppTable, then read that Context anduseSelector(table.store, state => state)in the extracted body.There is one Compiler detail to retain: put the imperative row-model read in a small hook that keeps its explicit dependencies:
With Compiler 1.0.0, the same
useMemoinside a compiled component had its extra dependencies removed in the generated output, so Context alone still left stale rows. This keeps only the small hook outside compilation; the owner, intermediate component and table body remain compiled. Treat it as a temporary compatibility boundary and recheck after upgrading.Here is a complete runnable example using Table 9.2.4 and React 19.3.0. Browser checks covered repeated data replacement, two-second updates, sorting/filtering, both page-reset settings, and empty/refilled data through memoized components. It uses fixed columns and React state; it has not been checked with a live Query client or WebSocket transport.
Also,
table.optionsStorewasundefinedin the checked React adapter, so the generic options-store subscription example does not provide a drop-in solution for this version.Bhavani2016 commentedon Oct 6, 2026
Hi, I reproduced this locally with React Compiler enabled on 9.2.4. Disabling the compiler makes both tables update.
I also added a test on the 9.2.6 checkout that keeps the
AppTablesubtree memoized. After replacing the data, the subscribed component still displays “Ada” instead of “Grace”. The existing tests pass.From what I traced,
table.Subscribelistens totable.storeby default, while the updated data is intable.options.data. A data-only change therefore doesn’t appear to notify that subscription. As an experiment on 9.2.4, subscribing to a readonly atom aroundtable.getRowModel()made both tables update, though this uses the internal_reactivityAPI.I saw the Context workaround above and would like to help. Would you prefer a supported way to subscribe to row-model changes, or a docs/example update showing the recommended approach? I’m happy to share the test.
gorkemkaramolla commentedon Oct 11, 2026
I looked into this and opened #6631 for the
createTableHookpart.The cause:
useAppTablecreatesAppTableonce and reads the table from a ref. When the parent's output is cached (the compiler caches<table.AppTable>…</table.AppTable>sincetable.AppTableis stable), React skipsAppTable, so its context keeps an old table. Replacingdatadoesn't change any table state, soSubscribecan't catch it either. Preact has the same issue.#6631 makes
AppTablere-render when the owner commits a new table, without extra renders in the normal case. That should fix thetable1case from the sandbox without any workaround.One thing it doesn't cover: components that get
tableas a plain prop outsideAppTablestill have nothing to subscribe to for data changes. TheSubscribedocs mentionsource={table.optionsStore}, buttable.optionsStoreisundefinedin React/Preact becauserenderPhaseReactivitysetscreateOptionsStore: false.Would you be open to exposing
table.optionsStoreas a read-only atom in React/Preact (updated after commit), so<table.Subscribe source={table.optionsStore} selector={(o) => o.data}>works? Or would you rather remove that from the docs and recommend passingdataalong withtable? Happy to do either.Bhavani2016 commentedon Oct 12, 2026
Thanks for the fix! I tested PR #6631 locally. My regression test, which previously failed with “Ada” instead of “Grace,” now passes. All 37 React adapter unit tests also pass.
I also tested the two-table data-refresh reproduction against the local PR build with React Compiler enabled. Both tables now update their timestamps and row order every two seconds without the workaround.
fix(react-table, preact-table): refresh AppTable context under cached…
tevinch commentedon Oct 13, 2026
The
AppTablepart now has a released fix:@tanstack/react-table@9.3.1includes #6631.I checked the npm release with React 19.3.0 and React Compiler 1.0.0. The example now uses
useTableContext()insideAppTable, with row-model reads insidetable.Subscribefor feature-state changes. It no longer needs the extra data Context oruse no memohook.Chrome checks passed external data replacement, sorting/filtering, preserving page 2 when reset is disabled, emptying/refilling data, resetting to page 1 when enabled, and three successive two-second refreshes. The same code on 9.2.8 still displays the initial rows after the first update.
The updated example and runnable checks cover that context-based path. Components receiving
tableonly as a prop outsideAppTableremain a separate case; the official guide recommends also passingdataor the rendered rows through props.