Capítulo 27 de 54
TanStack Table stays deliberately lean — not every real feature request belongs in core — so v9 formalizes the plugin mechanism it has always supported by composition: write a TableFeature object with the same lifecycle hooks the built-in features use, register it in tableFeatures({...}), and it gets the exact same state/options/API treatment as rowSortingFeature or rowPaginationFeature.
TableFeature object is a set of optional lifecycle hooks. The three you'll use most: getInitialState (seed this feature's state slice — must spread ...initialState last so caller overrides win), getDefaultTableOptions (default option values, e.g. enableDensity: true), constructTableAPIs (attach methods to the table instance, e.g. table.toggleDensity()).assign<X>Prototype (add a method to the shared header/column/row/cell prototype, e.g. column.toggleSorting()) + init<X>InstanceData (per-instance mutable data/caches that can't live on a shared prototype — headers/rows rerun this on every rebuild; cells run it once, lazily, per row/column pair since cells are constructed on first access and cached). initHeaderGroupInstanceData is header groups' only per-instance extension point (they have no shared prototype).initTableInstanceData/resetTableInstanceData: for mutable, non-reactive per-table data (an interaction anchor, an imperative cache) — not table state. Runs once after options/atoms/store exist; features initialize in registration order, each feature's init hook running just before its own constructTableAPIs. resetTableInstanceData clears that data on table.reset(), which runs after internal state atoms are restored to initialState — it does not reset externally-controlled state and does not rerun initTableInstanceData.Plugins, TableState_FeatureMap, TableOptions_FeatureMap, and Table_FeatureMap (via declare module '@tanstack/react-table' { ... }) with your feature's key mapped to its state/options/API interfaces. Only then does TypeScript expose the new state/options/APIs — and only on tables whose features actually include that key.TableState_*/TableOptions_*/Table_* interfaces for the new state/options/APIs; (2) merge them into the four feature-map interfaces above; (3) write the TableFeature object implementing the relevant hooks; (4) register it: tableFeatures({ myPlugin }), pass to useTable.useState + your own handler functions, kept entirely outside the table instance, is equally legitimate for simpler cases — reach for a real custom feature when you want the state to live alongside built-in state (readable via table.atoms, resettable via table.reset(), type-safe on the table instance) rather than as ad hoc component state.A minimal "table density" (cell padding: sm/md/lg) custom feature, in outline:
type DensityState = 'sm' | 'md' | 'lg'
interface TableState_Density { density: DensityState }
interface TableOptions_Density { enableDensity?: boolean; onDensityChange?: OnChangeFn<DensityState> }
interface Table_Density { setDensity: (u: Updater<DensityState>) => void; toggleDensity: (v?: DensityState) => void }
declare module '@tanstack/react-table' {
interface Plugins { densityPlugin: TableFeature }
interface TableState_FeatureMap { densityPlugin: TableState_Density }
interface TableOptions_FeatureMap<TF extends TableFeatures, TD extends RowData> { densityPlugin: TableOptions_Density }
interface Table_FeatureMap<TF extends TableFeatures, TD extends RowData> { densityPlugin: Table_Density }
}
const densityPlugin: TableFeature = {
getInitialState: (initialState) => ({ density: 'md', ...initialState }), // caller overrides last
getDefaultTableOptions: (table) => ({
enableDensity: true,
onDensityChange: makeStateUpdater('density', table),
}),
constructTableAPIs: (table) => {
assignTableAPIs('densityPlugin', table, {
table_setDensity: { fn: (u) => table.options.onDensityChange?.(u) },
table_toggleDensity: { fn: (v) => table.options.onDensityChange?.(/* cycle sm→md→lg or use v */ v) },
})
},
}
// usage: register, then wire like any controlled slice
const features = tableFeatures({ densityPlugin })
const [density, setDensity] = useState<DensityState>('md')
const table = useTable({ features, columns, data, state: { density }, onDensityChange: setDensity })
// consume: style={{ padding: density === 'sm' ? 4 : density === 'md' ? 8 : 16 }}
This is the general shape any custom feature follows — declare types, merge into the feature maps, implement the hooks that actually apply (most features only need getInitialState + constructTableAPIs), register, wire like a normal controlled state slice.
table.atoms/table.reset() mechanics, same tree-shaking (a feature not registered ships no code).getInitialState and constructTableAPIs; prototype/instance-data hooks are for row/column/cell/header-level extensions.table.atoms/table.reset().tableFeatures() registration mechanism, applied to built-in features.initialState, external control).TableFeature, TableFeatures, and related type definitions in full.