Capítulo 14 de 54
An experimental plugin (experimental-worker-plugin) that moves the expensive row-model stages (filtering, grouping+aggregation, sorting) into a Web Worker so recomputation never blocks the main thread — for the narrow case of 100,000+ client-side rows where server-side processing genuinely isn't an option.
manual* processing, pagination, and virtualization first — reach for this only when the full dataset must live client-side (local-first apps, offline caches, analytics grids) and row-count × comparator cost exceeds a frame budget (~100k+ rows for basic sorts, fewer for expensive custom filter/sort functions).tableFeatures) is imported by both the main thread and a worker entry file; the worker runs a headless "shadow table" executing the real row-model pipeline and posts back compact results (index permutations, or serialized row trees when grouping is active). The UI keeps rendering the previous rows while a computation is in flight.accessorKey columns (or accessors defined in that same module), registry functions (sortFns/filterFns/aggregationFns), no closures over app state.createWorkerRowModel(tableWorker, stage) where stage is 'filtered' | 'grouped' | 'sorted' | 'expanded'. Offloaded stages must be a contiguous prefix of the pipeline — offloading filtered alone is fine, but offloading sorted while filtering stays on the main thread would silently skip the filter (a dev-mode warning fires on this mismatch). Never offload expanded (changes every toggle); keep pagination main-thread always (it just slices a page).table.state.workerRowModels: .isPending, .lastComputeMs, .lastRoundTripMs.getSubRows); processing-affecting options (custom globalFilterFn, etc.) must be passed to initTableWorker in the shared config, not just to the table hook; grouping aggregates need both rowAggregationFeature and columnGroupingFeature in the shared features and only compute for columns with an explicit aggregationFn/aggregatedCell; custom aggregation results must survive the browser's structured-clone algorithm; grand totals via column.getAggregationValue() always run main-thread, not in the worker.tableWorker.terminate() is a manual escape hatch (nothing auto-terminates yet) and self-heals on the next read.Worker global (server), the table renders unprocessed rows; the client takes over post-hydration./experimental-worker-plugin subpath, fully tree-shakable (unused = unshipped).// tableConfig.ts — imported by BOTH app and worker, must be thread-portable
export const sharedFeatures = tableFeatures({
rowSortingFeature, columnFilteringFeature, rowPaginationFeature,
workerRowModelsFeature,
filteredRowModel: createFilteredRowModel(),
sortedRowModel: createSortedRowModel(),
paginatedRowModel: createPaginatedRowModel(), // stays on main thread
sortFns, filterFns,
})
// table.worker.ts — the entire worker file
import { initTableWorker } from '@tanstack/react-table/experimental-worker-plugin'
initTableWorker({ features: sharedFeatures, columns })
// App.tsx — swap offloaded stages for worker-backed ones
const tableWorker = createTableWorker({
createWorker: () => new Worker(new URL('./table.worker.ts', import.meta.url), { type: 'module' }),
})
const features = {
...sharedFeatures,
filteredRowModel: createWorkerRowModel(tableWorker, 'filtered'),
sortedRowModel: createWorkerRowModel(tableWorker, 'sorted'),
}
const table = useTable({ features, columns, data }) // otherwise a completely normal table
filteredRowModel/sortedRowModel.