When to use: first table, no optional features yet.
How: tableFeatures({}) → columnHelper.columns([...]) → useTable({ key, features, columns, data }) → render from table.getHeaderGroups()/table.getRowModel().rows via table.FlexRender.
Trade-offs: no sorting/filtering/pagination until features are added — but the smallest possible bundle.
When to use: dataset too large/expensive to load fully client-side.
How: register the features (rowPaginationFeature, rowSortingFeature, columnFilteringFeature) without their row-model factories; set manualPagination/manualSorting/manualFiltering: true; own pagination/sorting/columnFilters via external atoms (or state+on*Change); feed them into a TanStack Query key; reset pageIndex to 0 by hand in every non-pagination change handler (the automatic reset doesn't fire under manual mode).
Trade-offs: more wiring than client-side, but scales to arbitrarily large data; requires backend cooperation for every operation.
When to use: more than one table shares features/defaults/conventions.
How: createTableHook({ features, ...defaults }) once → useAppTable/createAppColumnHelper per table. Add tableComponents/cellComponents/headerComponents only if UI pieces (toolbars, cell renderers) should also be shared.
Trade-offs: upfront setup cost; pays off once there are 2+ tables with overlapping needs.
When to use: a table with expensive cells or many rows where broad re-renders are visibly slow.
How: pass () => null as useTable's selector to opt the owning component out of table-state re-renders; wrap the parts that must react (row model render, a selection checkbox, a filter indicator) in table.Subscribe with a narrow selector (or source scoped to one atom).
Trade-offs: more components, more subscription boilerplate; only worth it once profiling shows a real cost.
When to use: a component whose only props are a stable row/cell/column/header and that calls a state-dependent method on it (row.getIsSelected(), column.getIsSorted()).
How: wrap the state-dependent read in Subscribe/table.Subscribe (or useSelector) inside that component, scoped to the narrowest relevant slice/atom — never rely on the parent's re-render to keep it fresh.
Trade-offs: none — this is required correctness under the compiler, not an optimization choice.
When to use: approximate/typo-tolerant search that should also rank results by match quality.
How: rankItem from @tanstack/match-sorter-utils in a custom filterFn, stashing itemRank via addMeta; a paired sortFn reads row.columnFiltersMeta[columnId] and falls back to alphanumeric on ties; register both under the same name ('fuzzy'), typed via the filterMeta phantom slot.
Trade-offs: extra dependency and setup vs. plain includesString; worth it for search-as-you-type UX.
When to use: filtering a high-cardinality numeric/date column (file size, price, last-login date) where raw unique values are useless in a UI.
How: getUniqueValues on the column emits a bucket key (e.g. '1-to-10-gb') for faceting; a matching constructFilterFn({ resolveDataValue: sameBucketFn, filter: (bucket, selected) => selected.includes(bucket) }) filters by the same bucket definition; keep the raw accessor value untouched for rendering/other features.
Trade-offs: bucket boundaries must be shared/kept in sync between faceting and filtering, or counts and results disagree.
When to use: large/complex tables where 'onChange' resize mode stutters.
How: subscribe the table-owning component to no resize state (() => ({})); write column widths as CSS custom properties imperatively in a useLayoutEffect subscribed to table.atoms.columnSizing; isolate only the visually-reactive bits (active-resizer highlight) into small table.Subscribe islands.
Trade-offs: more imperative code, bypasses normal React data flow for width — reserve for tables that actually need it.
When to use: new state/options/table-instance-methods that should live alongside built-in features (visible in table.atoms, resettable via table.reset()).
How: declare TableState_*/TableOptions_*/Table_* interfaces → merge into Plugins/TableState_FeatureMap/TableOptions_FeatureMap/Table_FeatureMap via declare module → implement getInitialState/getDefaultTableOptions/constructTableAPIs (+ prototype/instance-data hooks as needed) → register in tableFeatures({ myPlugin }).
Trade-offs: more ceremony than plain component state; worth it specifically when the state needs to behave like a first-class table feature.