Capítulo 38 de 54
Faceting answers "which filter choices remain," not "which rows remain" (that's filtering) or "what's the summary value" (that's aggregation) — a column's faceted row model excludes that column's own filter while applying every other active filter, which is exactly what lets facets narrow each other without a facet ever hiding its own alternatives.
aggregationFn.Region=Europe and Plan=Pro are both active, the Plan facet's counts are computed from all Europe rows (region filter applied, plan filter excluded) so the user still sees every plan option; a Status facet, meanwhile, does see the plan filter applied, since it's not the Status column's own filter.columnFacetingFeature + columnFilteringFeature, plus row-model factories matched to what you need: facetedRowModel: createFacetedRowModel() (required for any client-side faceting — without it, facets fall back to pre-filtered rows and stop reacting to other filters), facetedUniqueValues: createFacetedUniqueValues() (value→count maps), facetedMinMaxValues: createFacetedMinMaxValues() (numeric range).column.getFacetedRowModel() (rows passing every filter but this column's own — for custom calculations), column.getFacetedUniqueValues() (a Map<value, count> — checkboxes, selects, autocomplete), column.getFacetedMinMaxValues() ([min, max] or undefined — range sliders/number inputs).getUniqueValues: (row) => [...] (e.g. a tags array) — then counts are occurrence counts, not row counts, unless getUniqueValues is written to emit each value at most once per row.column object, wrap the component rendering facet options in table.Subscribe selecting state.columnFilters (or whatever slice should trigger recompute) — otherwise it won't update when a sibling filter changes.getUniqueValues to emit a bucket key while keeping the accessor's raw value for everything else, and build a matching custom filterFn (via constructFilterFn, resolveDataValue mapping the raw value to the same bucket key) so facet counts and filter results always agree — no hidden derived column needed.facetedUniqueValues/facetedMinMaxValues factories — each receives (table, columnId) once and must return a function that resolves the live result on every read (not cached by the table itself), so read from table.options.meta/a store/signal inside that returned function to reflect fresh server data, and memoize inside the factory yourself if the calculation is expensive. A server query for one column should mirror the built-in behavior: apply every other active filter, exclude that column's own.table.getGlobalFacetedRowModel(), getGlobalFacetedUniqueValues(), getGlobalFacetedMinMaxValues(), powered by the same factories. Requires globalFilteringFeature. Custom factories receive the special column id '__global__' for these requests, so a server-backed implementation can branch on it.const features = tableFeatures({
columnFacetingFeature, columnFilteringFeature,
filteredRowModel: createFilteredRowModel(),
facetedRowModel: createFacetedRowModel(),
facetedUniqueValues: createFacetedUniqueValues(),
facetedMinMaxValues: createFacetedMinMaxValues(),
})
// reactive facet options, local subscription
<table.Subscribe selector={(s) => s.columnFilters}>
{() => Array.from(column.getFacetedUniqueValues()).map(([value, count]) => (
<label key={String(value)}>{String(value)} ({count})</label>
))}
</table.Subscribe>
table.Subscribe wrapper needed for facet options to react to sibling-filter changes, plus the unique-values-map-to-list conversion.facetedUniqueValues vs facetedMinMaxValues vs both) — each is independently optional.getFacetedUniqueValues() — keep the bucket definition shared between faceting (getUniqueValues) and filtering (resolveDataValue) so counts and results agree.constructFilterFn, resolveDataValue, the filter side of the bucketing pattern.