Summary
In @databricks/appkit-ui, DataTable / TableWrapper calls transform(data) on every render and passes the result straight to useReactTable as data. A typical rows.map(...) returns a new array identity each time. TanStack Table treats that as a data change, auto-resets pageIndex, React re-renders, and the tab freezes.
Sorting is just the first user action that re-renders. Filter, pagination, and any parent state change do the same.
Confirmed in 0.38.1 (reproduced in a production app) and still present in 0.66.1 (packages dist table-wrapper.js).
There is no React "maximum update depth" overlay: TanStack schedules the page-index reset through microtasks, so the main thread just locks. CDP samples during the freeze sit in performSyncWorkOnRoot → TableWrapper. Network is idle.
Reproduction
<DataTable
queryKey="some_query"
parameters={{}}
transform={(rows) => rows.map((row) => ({ ...row, n: Number(row.n) }))}
/>
- Wait for rows to load.
- Click any column header to sort.
- The tab becomes unresponsive. No error, no extra analytics requests.
Without transform, sorting works.
Expected
transform should run when query data changes, not on every render. Sort, filter, and pagination should stay interactive.
Root cause
TableWrapper currently does:
const processedData = hasData ? (transformer ? transformer(data) : data) : [];
useReactTable({ data: processedData, /* ... */ });
columns is also useMemo'd with processedData in the dependency list, so a new array also rebuilds column defs every render.
useAnalyticsQuery already solves a similar identity problem with useStableParams. transform has no equivalent.
TanStack's default autoResetPageIndex turns "new array every render" into a setState loop instead of just wasted work.
Suggested fix
Memoize processed rows on query data, and keep the latest transformer in a ref so an inline transform prop does not retrigger the memo:
const transformerRef = useRef(transformer);
transformerRef.current = transformer;
const processedData = useMemo(() => {
if (!data?.length) return [];
return transformerRef.current ? transformerRef.current(data) : data;
}, [data]);
Also:
- Memoize generated columns on column keys / first-row shape, not on the new
processedData array.
- Optionally set
autoResetPageIndex: false, or only reset when data actually changes.
useCallback on the consumer's transform is not sufficient with the current code: even a stable function still returns a new array, and that array is not memoized.
Workaround
Cache transform output by input-array identity (useRef), or skip transform and reshape in SQL.
Environment
@databricks/appkit / @databricks/appkit-ui 0.38.1 (repro), 0.66.1 (source still has the pattern)
- React 19
DataTable opinionated mode with a transform prop
Summary
In
@databricks/appkit-ui,DataTable/TableWrappercallstransform(data)on every render and passes the result straight touseReactTableasdata. A typicalrows.map(...)returns a new array identity each time. TanStack Table treats that as a data change, auto-resetspageIndex, React re-renders, and the tab freezes.Sorting is just the first user action that re-renders. Filter, pagination, and any parent state change do the same.
Confirmed in 0.38.1 (reproduced in a production app) and still present in 0.66.1 (
packagesdisttable-wrapper.js).There is no React "maximum update depth" overlay: TanStack schedules the page-index reset through microtasks, so the main thread just locks. CDP samples during the freeze sit in
performSyncWorkOnRoot→TableWrapper. Network is idle.Reproduction
Without
transform, sorting works.Expected
transformshould run when querydatachanges, not on every render. Sort, filter, and pagination should stay interactive.Root cause
TableWrappercurrently does:columnsis alsouseMemo'd withprocessedDatain the dependency list, so a new array also rebuilds column defs every render.useAnalyticsQueryalready solves a similar identity problem withuseStableParams.transformhas no equivalent.TanStack's default
autoResetPageIndexturns "new array every render" into a setState loop instead of just wasted work.Suggested fix
Memoize processed rows on query
data, and keep the latest transformer in a ref so an inlinetransformprop does not retrigger the memo:Also:
processedDataarray.autoResetPageIndex: false, or only reset whendataactually changes.useCallbackon the consumer'stransformis not sufficient with the current code: even a stable function still returns a new array, and that array is not memoized.Workaround
Cache transform output by input-array identity (
useRef), or skiptransformand reshape in SQL.Environment
@databricks/appkit/@databricks/appkit-ui0.38.1 (repro), 0.66.1 (source still has the pattern)DataTableopinionated mode with atransformprop