Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
"label": "angular",
"children": [
{ "label": "Quick Start", "to": "framework/angular/quick-start" },
{ "label": "Migrating to V9", "to": "framework/angular/guide/migrating" },
{ "label": "Rendering components", "to": "framework/angular/guide/rendering" }
{ "label": "Migrating to V9", "to": "framework/angular/guide/migrating" }
]
},
{
Expand Down Expand Up @@ -94,11 +93,12 @@
{
"label": "Core Guides",
"children": [
{ "label": "Features", "to": "guide/features" },
{ "label": "Data", "to": "guide/data" },
{ "label": "Client-Side vs Server-Side", "to": "guide/client-side-vs-server-side" },
{ "label": "Column Definitions", "to": "guide/column-defs" },
{ "label": "Table Instance", "to": "guide/tables" },
{ "label": "Row Models", "to": "guide/row-models" },
{ "label": "Aggregation", "to": "guide/aggregation" },
{ "label": "Worker Row Models (Experimental)", "to": "guide/worker-row-models" },
{ "label": "Rows", "to": "guide/rows" },
{ "label": "Cells", "to": "guide/cells" },
Expand All @@ -120,6 +120,7 @@
"label": "Composable Tables (createTableHook)",
"to": "framework/alpine/guide/composable-tables"
},
{ "label": "FlexRender", "to": "framework/alpine/guide/flex-render" },
{
"label": "Custom Plugins",
"to": "framework/alpine/guide/custom-features"
Expand All @@ -131,6 +132,7 @@
"children": [
{ "label": "Table State", "to": "framework/angular/guide/table-state" },
{ "label": "Composable Tables (createTableHook)", "to": "framework/angular/guide/composable-tables" },
{ "label": "FlexRender", "to": "framework/angular/guide/flex-render" },
{ "label": "Custom Plugins", "to": "framework/angular/guide/custom-features" }
]
},
Expand All @@ -139,6 +141,7 @@
"children": [
{ "label": "Table State", "to": "framework/ember/guide/table-state" },
{ "label": "Composable Tables (createTableHook)", "to": "framework/ember/guide/composable-tables" },
{ "label": "FlexRender", "to": "framework/ember/guide/flex-render" },
{ "label": "Custom Plugins", "to": "framework/ember/guide/custom-features" }
]
},
Expand All @@ -147,6 +150,7 @@
"children": [
{ "label": "Table State", "to": "framework/lit/guide/table-state" },
{ "label": "Composable Tables (createTableHook)", "to": "framework/lit/guide/composable-tables" },
{ "label": "FlexRender", "to": "framework/lit/guide/flex-render" },
{ "label": "Custom Plugins", "to": "framework/lit/guide/custom-features" }
]
},
Expand All @@ -156,6 +160,7 @@
{ "label": "Table State", "to": "framework/react/guide/table-state" },
{ "label": "Composable Tables (createTableHook)", "to": "framework/react/guide/composable-tables" },
{ "label": "Table Context", "to": "framework/react/guide/table-context" },
{ "label": "FlexRender", "to": "framework/react/guide/flex-render" },
{ "label": "Custom Plugins", "to": "framework/react/guide/custom-features" }
]
},
Expand All @@ -165,6 +170,7 @@
{ "label": "Table State", "to": "framework/preact/guide/table-state" },
{ "label": "Composable Tables (createTableHook)", "to": "framework/preact/guide/composable-tables" },
{ "label": "Table Context", "to": "framework/preact/guide/table-context" },
{ "label": "FlexRender", "to": "framework/preact/guide/flex-render" },
{ "label": "Custom Plugins", "to": "framework/preact/guide/custom-features" }
]
},
Expand All @@ -174,6 +180,7 @@
{ "label": "Table State", "to": "framework/octane/guide/table-state" },
{ "label": "Composable Tables (createTableHook)", "to": "framework/octane/guide/composable-tables" },
{ "label": "Table Context", "to": "framework/octane/guide/table-context" },
{ "label": "FlexRender", "to": "framework/octane/guide/flex-render" },
{ "label": "Custom Plugins", "to": "framework/octane/guide/custom-features" }
]
},
Expand All @@ -182,6 +189,7 @@
"children": [
{ "label": "Table State", "to": "framework/solid/guide/table-state" },
{ "label": "Composable Tables (createTableHook)", "to": "framework/solid/guide/composable-tables" },
{ "label": "FlexRender", "to": "framework/solid/guide/flex-render" },
{ "label": "Custom Plugins", "to": "framework/solid/guide/custom-features" }
]
},
Expand All @@ -190,6 +198,7 @@
"children": [
{ "label": "Table State", "to": "framework/svelte/guide/table-state" },
{ "label": "Composable Tables (createTableHook)", "to": "framework/svelte/guide/composable-tables" },
{ "label": "FlexRender", "to": "framework/svelte/guide/flex-render" },
{ "label": "Custom Plugins", "to": "framework/svelte/guide/custom-features" }
]
},
Expand All @@ -198,13 +207,15 @@
"children": [
{ "label": "Table State", "to": "framework/vue/guide/table-state" },
{ "label": "Composable Tables (createTableHook)", "to": "framework/vue/guide/composable-tables" },
{ "label": "FlexRender", "to": "framework/vue/guide/flex-render" },
{ "label": "Custom Plugins", "to": "framework/vue/guide/custom-features" }
]
},
{
"label": "vanilla",
"children": [
{ "label": "Table State", "to": "framework/vanilla/guide/table-state" }
{ "label": "Table State", "to": "framework/vanilla/guide/table-state" },
{ "label": "FlexRender", "to": "framework/vanilla/guide/flex-render" }
]
}
]
Expand Down
174 changes: 154 additions & 20 deletions docs/framework/alpine/guide/aggregation.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ Aggregation is independent from column grouping. Register `rowAggregationFeature
whenever columns calculate totals or aggregated values. Add
`columnGroupingFeature` separately only when the table also groups rows.

For the complete behavior and type reference, see the core
[Aggregation Guide](../../../guide/aggregation).

## Aggregation Setup

Register only the built-in functions referenced by name. Passing a definition
Expand Down Expand Up @@ -50,6 +47,11 @@ const table = createTable({
The aggregation feature does not require a grouped row model. This makes grand
totals and custom row-subset totals available in otherwise ordinary tables.

The full `aggregationFns` registry remains available for compatibility, but it
bundles every built-in. Tables using `stockFeatures` already include
`rowAggregationFeature`; they still need the definitions that named column
options should resolve to.

## Column Aggregations

A column accepts one aggregation or an array. A single entry returns a scalar;
Expand All @@ -69,6 +71,28 @@ columnHelper.accessor('score', {
String values remain backward-compatible. Use descriptors when a result needs
a stable custom key or options.

A scalar `aggregationFn` can be a registered name, `'auto'`, or an inline
definition. Every entry in an aggregation array needs a unique stable id.
Duplicate ids, missing descriptor ids, and unregistered names warn in
development and preserve the affected key with an `undefined` value.

Multiple aggregations can be read with a typed result:

```ts
const scoreColumn = columnHelper.accessor('score', {
aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }],
footer: ({ column }) => {
const result = column.getAggregationValue<{
count: number
mean: number | undefined
range: [number | undefined, number | undefined]
}>()

return `${result.count} values; mean ${result.mean}; range ${result.range}`
},
})
```

## Grand Totals and Row Subsets

Call `column.getAggregationValue()` without arguments to aggregate the default
Expand All @@ -90,13 +114,26 @@ column.getAggregationValue({ rows: table.getCoreRowModel().rows, maxDepth: 1 })
```

Depth is relative to the supplied row array. `0` selects those roots, `1`
selects their direct sub-rows, and `Infinity` selects terminal rows. Configure
`maxAggregationDepth` on the column for cached default calls, or pass
`maxDepth` in the options object as an explicit override.
`table.getMaxSubRowDepth()` returns
the deepest structural depth in the core row model. Column option
`getAggregationValue(context)` can provide an external or server-computed
value; return `undefined` to fall back to the configured aggregation function.
selects their direct sub-rows, and so on. Selection returns a unique frontier:
a branch that ends before the maximum depth contributes its deepest available
row. `Infinity` selects terminal rows.

Configure `maxAggregationDepth` on the column for cached default calls (it
defaults to `0`), or pass `maxDepth` in the options object as an explicit
override. Every aggregation configured on the column receives the same
selected rows. Explicit row calls are recomputed each time; the default call is
cached against its row model, depth, registry, and column aggregation option.

`table.getMaxSubRowDepth()` returns the deepest structural depth in the core
row model. To stop one level before the deepest sub-row frontier:

```ts
const maxDepth = Math.max(0, table.getMaxSubRowDepth() - 1)
column.getAggregationValue({
rows: table.getCoreRowModel().rows,
maxDepth,
})
```

## Grouped Aggregation

Expand All @@ -119,17 +156,114 @@ columnHelper.accessor('visits', {
})
```

Use `cell.getIsAggregated()` to identify a grouped aggregate cell. Footer
rendering uses the adapter's normal footer renderer.
The `aggregatedCell` column option renders aggregate values on synthetic
grouped rows. Use `cell.getIsAggregated()` to identify a grouped aggregate
cell. Footer rendering uses the adapter's normal footer renderer. Grouping-only
tables do not expose `cell.getIsAggregated()`; it belongs to
`rowAggregationFeature`.

## Custom Aggregation Definitions

Use `constructAggregationFn({ aggregate, merge? })` for custom definitions.
The aggregate context includes depth-selected `rows`, `maxDepth`, `getValue`,
`column`, and `table`. Every aggregation configured on a column receives the
same row frontier. Grouped calls also include `groupingRow` and immediate
`subRows` for custom structural behavior. A `merge` implementation can more
efficiently combine already-computed sub-row results.
Custom aggregations are context-based definitions. `rows` contains the unique
frontier selected at `maxDepth`, and `getValue(row)` reads the current column's
value.

```ts
const joined = constructAggregationFn<any, any, string, string>({
aggregate: ({ rows, getValue }) =>
rows
.map((row) => getValue(row))
.filter(Boolean)
.join(', '),
})
```

The context also includes `column`, `columnId`, `maxDepth`, and `table`. During
grouped aggregation it includes `groupingRow` and `subRows`; root and
caller-supplied-row aggregation omit those properties. The grouping depth is
`groupingRow.depth`. `subRows` contains the immediate rows at that grouping
level, so an aggregation can explicitly choose immediate sub-rows instead of
the depth-selected `rows`:

```ts
const subRowCount = constructAggregationFn<any, any, unknown, number>({
aggregate: ({ subRows, rows }) => (subRows ?? rows).length,
})
```

At the terminal grouping level, `subRows` contains direct data rows. At a
nested level, it contains the immediate synthetic sub-row groups. All built-in
aggregation definitions consume the same depth-selected `rows`; `subRows`
remains available when a custom definition intentionally needs the grouping
row's immediate structural children.

For a result that can be combined more efficiently from already-computed
sub-row results, provide a `merge` function:

```ts
const sum = constructAggregationFn<any, any, unknown, number>({
aggregate: ({ rows, getValue }) =>
rows.reduce((total, row) => {
const value = getValue(row)
return total + (typeof value === 'number' ? value : 0)
}, 0),
merge: ({ subRowResults }) =>
subRowResults.reduce((total, value) => total + value, 0),
})
```

For `merge`, `subRowResults[i]` is the aggregation result previously computed
for `subRows[i]`. Without `merge`, nested grouping calls `aggregate` with both
the group's depth-selected `rows` and its immediate `subRows`. This
context-based form replaces the previous callable aggregation signature and its
`fromRows` and `resolveDataValue` properties while preserving access to both
row sets.

## Providing Server or External Values

A column can handle aggregation-value requests before local calculation:

```ts
const amountColumn = columnHelper.accessor('amount', {
aggregationFn: 'sum',
getAggregationValue: ({ rows }) => {
if (rows !== undefined) return undefined // use local fallback for overrides
return { value: serverTotals.amount }
},
})
```

Returning `{ value }` marks the request as handled, including
`{ value: undefined }`. Returning `undefined` uses the local fallback. Put the
same provider on `defaultColumn` to share it across columns.

Set `manualAggregation: true` to disable the local fallback for
`column.getAggregationValue()`. This is separate from `manualGrouping`, which
controls whether the grouped row model runs. See the
[Client-Side vs Server-Side Guide](../../../guide/client-side-vs-server-side)
for guidance on choosing where the full data pipeline should run.

## Built-in Definitions

- `sum`: sums numeric values; non-numbers contribute zero.
- `count`: counts rows.
- `min` / `max`: find numeric or Date bounds.
- `extent`: returns `[min, max]`; an empty input returns
`[undefined, undefined]`.
- `mean`: averages numeric and number-like non-null values.
- `median`: requires every row value to be a number.
- `unique` / `uniqueCount`: use JavaScript `Set` semantics.
- `first` / `last`: return the positional value, including a nullish value.

`aggregationFn: 'auto'` inspects the first core row value. Numbers resolve to a
registered `sum`, Dates resolve to a registered `extent`, and other values do
not resolve an aggregation.

## Web Workers

See [Custom Aggregation Definitions](../../../guide/aggregation#custom-aggregation-definitions)
for the full contract, return typing, caching behavior, and worker limitations.
Worker-backed grouped row models eagerly compute explicitly configured grouped
aggregates in the worker. `column.getAggregationValue()` still executes its
final total on the main thread over the selected row model. Aggregation results
crossing the worker boundary must be structured-cloneable. See the
[Worker Row Models Guide](../../../guide/worker-row-models) for setup and
limitations.
16 changes: 3 additions & 13 deletions docs/framework/alpine/guide/column-filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,9 @@ TanStack table supports both client-side and manual server-side filtering. This

### Client-Side vs Server-Side Filtering

If you have a large dataset, you may not want to load all of that data into the client's browser in order to filter it. In this case, you will most likely want to implement server-side filtering, sorting, pagination, etc.
Filtering should operate over the same dataset as sorting and pagination. Use client-side filtering when the browser has the complete dataset; use server-side filtering when it has only a page or another subset, unless filtering just the loaded rows is intentional.

However, as also discussed in the [Pagination Guide](./pagination#should-you-use-client-side-pagination), a lot of developers underestimate how many rows can be loaded client-side without a performance hit. The TanStack table examples are often tested to handle up to 100,000 rows or more with decent performance for client-side filtering, sorting, pagination, and grouping. This doesn't necessarily mean that your app will be able to handle that many rows, but if your table is only going to have a few thousand rows at most, you might be able to take advantage of the client-side filtering, sorting, pagination, and grouping that TanStack table provides.

> TanStack Table can handle thousands of client-side rows with good performance. Don't rule out client-side filtering, pagination, sorting, etc. without some thought first.

Every use-case is different and will depend on the complexity of the table, how many columns you have, how large every piece of data is, etc. The main bottlenecks to pay attention to are:

1. Can your server query all of the data in a reasonable amount of time (and cost)?
2. What is the total size of the fetch? (This might not scale as badly as you think if you don't have many columns.)
3. Is the client's browser using too much memory if all of the data is loaded at once?

If you're not sure, you can always start with client-side filtering and pagination and then switch to server-side strategies in the future as your data grows.
See the [Client-Side vs Server-Side Guide](../../../guide/client-side-vs-server-side) for the full decision framework, performance factors, and guidance for combining data operations.

### Manual Server-Side Filtering

Expand Down Expand Up @@ -343,7 +333,7 @@ You can attach a few other properties to filter functions to customize their beh

- `filterFn.resolveDataValue` - This optional "hanging" method normalizes each row's value before it is compared against the filter value. It is honored by every filter function built with the `constructFilterFn` helper, which includes all built-in filter functions.

- `filterFn.autoRemove` - This optional "hanging" method on any given `filterFn` is passed a filter value and expected to return `true` if the filter value should be removed from the filter state. eg. Some boolean-style filters may want to remove the filter value from the table state if the filter value is set to `false`. When provided, this test is authoritative: values it keeps stay in filter state even when they are empty strings, which the default heuristic would otherwise remove. An `undefined` filter value always clears the filter regardless.
- `filterFn.autoRemove` - This optional "hanging" method on any given `filterFn` is passed a filter value and expected to return `true` if the filter value should be removed from the filter state. e.g. Some boolean-style filters may want to remove the filter value from the table state if the filter value is set to `false`. When provided, this test is authoritative: values it keeps stay in filter state even when they are empty strings, which the default heuristic would otherwise remove. An `undefined` filter value always clears the filter regardless.

The `constructFilterFn` helper builds a filter function from a value-level comparator plus those optional resolvers:

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/alpine/guide/custom-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,4 +405,4 @@ Alpine.data('table', () => {

### Do We Have to Do It This Way?

This is just a new way to integrate custom code along-side the built-in features in TanStack Table. In our example up above, we could have just as easily stored the `density` state in `Alpine.reactive`, defined our own `toggleDensity` handler wherever, and just used it in our code separately from the table instance. Building table features along-side TanStack Table instead of deeply integrating them into the table instance is still a perfectly valid way to build custom features. Depending on your use case, this may or may not be the cleanest way to extend TanStack Table with custom features.
This is just a new way to integrate custom code alongside the built-in features in TanStack Table. In our example up above, we could have just as easily stored the `density` state in `Alpine.reactive`, defined our own `toggleDensity` handler wherever, and just used it in our code separately from the table instance. Building table features alongside TanStack Table instead of deeply integrating them into the table instance is still a perfectly valid way to build custom features. Depending on your use case, this may or may not be the cleanest way to extend TanStack Table with custom features.
Loading
Loading