diff --git a/docs/config.json b/docs/config.json index 4bb8db4277..0071046caf 100644 --- a/docs/config.json +++ b/docs/config.json @@ -121,7 +121,7 @@ "to": "guide/tables" }, { - "label": "Rows Models", + "label": "Row Models", "to": "guide/row-models" }, { diff --git a/docs/guide/expanding.md b/docs/guide/expanding.md index c0f5174cce..d5856a0d7c 100644 --- a/docs/guide/expanding.md +++ b/docs/guide/expanding.md @@ -8,9 +8,222 @@ Want to skip to the implementation? Check out these examples: - [expanding](../../framework/react/examples/expanding) - [grouping](../../framework/react/examples/grouping) +- [sub-components](../../framework/react/examples/sub-components) ## API [Expanding API](../../api/features/expanding) -## Expanding Feature Guide \ No newline at end of file +## Expanding Feature Guide + +Expanding is a feature that allows you to show and hide additional rows of data related to a specific row. This can be useful in cases where you have hierarchical data and you want to allow users to drill down into the data from a higher level. Or it can be useful for showing additional information related to a row. + +### Different use cases for Expanding Features + +There are multiple use cases for expanding features in TanStack Table that will be discussed below. + +1. Expanding sub-rows (child rows, aggregate rows, etc.) +2. Expanding custom UI (detail panels, sub-tables, etc.) + +### Enable Client-Side Expanding + +To use the client-side expanding features, you need to define the getExpandedRowModel function in your table options. This function is responsible for returning the expanded row model. + +```ts +const table = useReactTable({ + // other options... + getExpandedRowModel: getExpandedRowModel(), +}) +``` + +Expanded data can either contain table rows or any other data you want to display. We will discuss how to handle both cases in this guide. + +### Table rows as expanded data + +Expanded rows are essentially child rows that inherit the same column structure as their parent rows. If your data object already includes these expanded rows data, you can utilize the `getSubRows` function to specify these child rows. However, if your data object does not contain the expanded rows data, they can be treated as custom expanded data, which is discussed in next section. + +For example, if you have a data object like this: + +```ts +type Person = { + id: number + name: string + age: number + children: Person[] +} + +const data: Person = [ + { id: 1, + name: 'John', + age: 30, + children: [ + { id: 2, name: 'Jane', age: 5 }, + { id: 5, name: 'Jim', age: 10 } + ] + }, + { id: 3, + name: 'Doe', + age: 40, + children: [ + { id: 4, name: 'Alice', age: 10 } + ] + }, +] +``` + +Then you can use the getSubRows function to return the children array in each row as expanded rows. The table instance will now understand where to look for the sub rows on each row. + +```ts +const table = useReactTable({ + // other options... + getSubRows: (row) => row.children, // return the children array as sub-rows + getCoreRowModel: getCoreRowModel(), + getExpandedRowModel: getExpandedRowModel(), +}) +``` + +> **Note:** You can have a complicated `getSubRows` function, but keep in mind that it will run for every row and every sub-row. This can be expensive if the function is not optimized. Async functions are not supported. + +### Custom Expanding UI + +In some cases, you may wish to show extra details or information, which may or may not be part of your table data object, such as expanded data for rows. This kind of expanding row UI has gone by many names over the years including "expandable rows", "detail panels", "sub-components", etc. + +By default, the `row.getCanExpand()` row instance API will return false unless it finds `subRows` on a row. This can be overridden by implementing your own `getRowCanExpand` function in the table instance options. + +```ts +//... +const table = useReactTable({ + // other options... + getRowCanExpand: (row) => true, // Add your logic to determine if a row can be expanded. True means all rows include expanded data + getCoreRowModel: getCoreRowModel(), + getExpandedRowModel: getExpandedRowModel(), +}) +//... + + {table.getRowModel().rows.map((row) => ( + + {/* Normal row UI */} + + {row.getVisibleCells().map((cell) => ( + + + + ))} + + {/* If the row is expanded, render the expanded UI as a separate row with a single cell that spans the width of the table */} + {row.getIsExpanded() && ( + + // The number of columns you wish to span for the expanded data if it is not a row that shares the same columns as the parent row + // Your custom UI goes here + + + )} + + ))} + +//... +``` + +### Expanded rows state + +If you need to control the expanded state of the rows in your table, you can do so by using the expanded state and the `onExpandedChange` option. This allows you to manage the expanded state according to your requirements. + +```ts +const [expanded, setExpanded] = useState({}) + +const table = useReactTable({ + // other options... + state: { + expanded: expanded, // must pass expanded state back to the table + }, + onExpandedChange: setExpanded +}) +``` + +The ExpandedState type is defined as follows: + +```ts +type ExpandedState = true | Record +``` + +If the ExpandedState is true, it means all rows are expanded. If it's a record, only the rows with IDs present as keys in the record and have their value set to true are expanded. For example, if the expanded state is { row1: true, row2: false }, it means the row with ID row1 is expanded and the row with ID row2 is not expanded. This state is used by the table to determine which rows are expanded and should display their subRows, if any. + +### UI toggling handler for expanded rows + +TanStack table will not add a toggling handler UI for expanded data to your table. You should manually add it within each row's UI to allow users to expand and collapse the row. For example, you can add a button UI within the columns definition. + +```ts +const columns = [ + { + accessorKey: 'name', + header: 'Name', + }, + { + accessorKey: 'age', + header: 'Age', + }, + { + header: 'Children', + cell: ({ row }) => { + return row.getCanExpand() ? + + : ''; + }, + }, +] +``` + +### Filtering Expanded Rows + +By default, the filtering process starts from the parent rows and moves downwards. This means if a parent row is excluded by the filter, all its child rows will also be excluded. However, you can change this behavior by using the `filterFromLeafRows` option. When this option is enabled, the filtering process starts from the leaf (child) rows and moves upwards. This ensures that a parent row will be included in the filtered results as long as at least one of its child or grandchild rows meets the filter criteria. Additionally, you can control how deep into the child hierarchy the filter process goes by using the `maxLeafRowFilterDepth` option. This option allows you to specify the maximum depth of child rows that the filter should consider. + +```ts +//... +const table = useReactTable({ + // other options... + getSubRows: row => row.subRows, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getExpandedRowModel: getExpandedRowModel(), + filterFromLeafRows: true, // search through the expanded rows + maxLeafRowFilterDepth: 1, // limit the depth of the expanded rows that are searched +}) +``` + +### Paginating Expanded Rows + +By default, expanded rows are paginated along with the rest of the table (which means expanded rows may span multiple pages). If you want to disable this behavior (which means expanded rows will always render on their parents page. This also means more rows will be rendered than the set page size) you can use the `paginateExpandedRows` option. + +```ts +const table = useReactTable({ + // other options... + paginateExpandedRows: false, +}) +``` + +### Pinning Expanded Rows + +Pinning expanded rows works the same way as pinning regular rows. You can pin expanded rows to the top or bottom of the table. Please refer to the [Pinning Guide](./pinning.md) for more information on row pinning. + +### Sorting Expanded Rows + +By default, expanded rows are sorted along with the rest of the table. + +### Manual Expanding (server-side) + +If you are doing server-side expansion, you can enable manual row expansion by setting the manualExpanding option to true. This means that the `getExpandedRowModel` will not be used to expand rows and you would be expected to perform the expansion in your own data model. + +```ts +const table = useReactTable({ + // other options... + manualExpanding: true, +}) +``` diff --git a/docs/guide/fuzzy-filtering.md b/docs/guide/fuzzy-filtering.md index 52afa9718e..6a42b8a839 100644 --- a/docs/guide/fuzzy-filtering.md +++ b/docs/guide/fuzzy-filtering.md @@ -14,3 +14,111 @@ Want to skip to the implementation? Check out these examples: ## Fuzzy Filtering Guide +Fuzzy filtering is a technique that allows you to filter data based on approximate matches. This can be useful when you want to search for data that is similar to a given value, rather than an exact match. + +You can implement a client side fuzzy filtering by defining a custom filter function. This function should take in the row, columnId, and filter value, and return a boolean indicating whether the row should be included in the filtered data. + +Fuzzy filtering is mostly used with global filtering, but you can also apply it to individual columns. We will discuss how to implement fuzzy filtering for both cases. + +> **Note:** You will need to install the `@tanstack/match-sorter-utils` library to use fuzzy filtering. +> TanStack Match Sorter Utils is a fork of [match-sorter](https://github.com/kentcdodds/match-sorter) by Kent C. Dodds. It was forked in order to work better with TanStack Table's row by row filtering approach. + +Using the match-sorter libraries is optional, but the TanStack Match Sorter Utils library provides a great way to both fuzzy filter and sort by the rank information it returns, so that rows can be sorted by their closest matches to the search query. + +### Defining a Custom Fuzzy Filter Function + +Here's an example of a custom fuzzy filter function: + +```typescript +import { rankItem } from '@tanstack/match-sorter-utils'; +import { FilterFn } from '@tanstack/table'; + +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info + addMeta({ itemRank }) + + // Return if the item should be filtered in/out + return itemRank.passed +} +``` + +In this function, we're using the rankItem function from the @tanstack/match-sorter-utils library to rank the item. We then store the ranking information in the meta data of the row, and return whether the item passed the ranking criteria. + +### Using Fuzzy Filtering with Global Filtering + +To use fuzzy filtering with global filtering, you can specify the fuzzy filter function in the globalFilterFn option of the table instance: + +```typescript +const table = useReactTable({ // or your framework's equivalent function + columns, + data, + filterFns: { + fuzzy: fuzzyFilter, //define as a filter function that can be used in column definitions + }, + globalFilterFn: 'fuzzy', //apply fuzzy filter to the global filter (most common use case for fuzzy filter) + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), //client side filtering + getSortedRowModel: getSortedRowModel(), //client side sorting needed if you want to use sorting too. +}) +``` + +### Using Fuzzy Filtering with Column Filtering + +To use fuzzy filtering with column filtering, you should first define the fuzzy filter function in the filterFns option of the table instance. You can then specify the fuzzy filter function in the filterFn option of the column definition: + +```typescript +const column = [ + { + accessorFn: row => `${row.firstName} ${row.lastName}`, + id: 'fullName', + header: 'Full Name', + cell: info => info.getValue(), + filterFn: 'fuzzy', //using our custom fuzzy filter function + }, + // other columns... +]; +``` + +In this example, we're applying the fuzzy filter to a column that combines the firstName and lastName fields of the data. + +#### Sorting with Fuzzy Filtering + +When using fuzzy filtering with column filtering, you might also want to sort the data based on the ranking information. You can do this by defining a custom sorting function: + +```typescript +import { compareItems } from '@tanstack/match-sorter-utils' +import { sortingFns } from '@tanstack/table' + +const fuzzySort: SortingFn = (rowA, rowB, columnId) => { + let dir = 0 + + // Only sort by rank if the column has ranking information + if (rowA.columnFiltersMeta[columnId]) { + dir = compareItems( + rowA.columnFiltersMeta[columnId]?.itemRank!, + rowB.columnFiltersMeta[columnId]?.itemRank! + ) + } + + // Provide an alphanumeric fallback for when the item ranks are equal + return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir +} +``` + +In this function, we're comparing the ranking information of the two rows. If the ranks are equal, we fall back to alphanumeric sorting. + +You can then specify this sorting function in the sortFn option of the column definition: + +```typescript +{ + accessorFn: row => `${row.firstName} ${row.lastName}`, + id: 'fullName', + header: 'Full Name', + cell: info => info.getValue(), + filterFn: 'fuzzy', //using our custom fuzzy filter function + sortFn: 'fuzzySort', //using our custom fuzzy sort function +} +``` diff --git a/docs/guide/global-faceting.md b/docs/guide/global-faceting.md index ff116b492e..708d2d53f4 100644 --- a/docs/guide/global-faceting.md +++ b/docs/guide/global-faceting.md @@ -12,4 +12,68 @@ Want to skip to the implementation? Check out these examples: [Global Faceting API](../../api/features/global-faceting) -## Global Faceting Guide \ No newline at end of file +## Global Faceting Guide + +Global Faceting allows you to generate lists of values for all columns from the table's data. For example, a list of unique values in a table can be generated from all rows in all columns to be used as search suggestions in an autocomplete filter component. Or, a tuple of minimum and maximum values can be generated from a table of numbers to be used as a range for a range slider filter component. + +### Global Faceting Row Models + +In order to use any of the global faceting features, you must include the appropriate row models in your table options. + +```ts +//only import the row models you need +import { + getCoreRowModel, + getFacetedRowModel, + getFacetedMinMaxValues, //depends on getFacetedRowModel + getFacetedUniqueValues, //depends on getFacetedRowModel +} from '@tanstack/react-table' +//... +const table = useReactTable({ + // other options... + getCoreRowModel: getCoreRowModel(), + getFacetedRowModel: getFacetedRowModel(), //Faceting model for client-side faceting (other faceting methods depend on this model) + getFacetedMinMaxValues: getFacetedMinMaxValues(), //if you need min/max values + getFacetedUniqueValues: getFacetedUniqueValues(), //if you need a list of unique values + //... +}) +``` + +### Use Global Faceted Row Models + +Once you have included the appropriate row models in your table options, you will be able to use the faceting table instance APIs to access the lists of values generated by the faceted row models. + +```ts +// list of unique values for autocomplete filter +const autoCompleteSuggestions = + Array.from(table.getGlobalFacetedUniqueValues().keys()) + .sort() + .slice(0, 5000); +``` + +```ts +// tuple of min and max values for range filter +const [min, max] = table.getGlobalFacetedMinMaxValues() ?? [0, 1]; +``` + +### Custom Global (Server-Side) Faceting + +If instead of using the built-in client-side faceting features, you can implement your own faceting logic on the server-side and pass the faceted values to the client-side. You can use the getGlobalFacetedUniqueValues and getGlobalFacetedMinMaxValues table options to resolve the faceted values from the server-side. + +```ts +const facetingQuery = useQuery( + 'faceting', + async () => { + const response = await fetch('/api/faceting'); + return response.json(); + }, + { + onSuccess: (data) => { + table.getGlobalFacetedUniqueValues = () => data.uniqueValues; + table.getGlobalFacetedMinMaxValues = () => data.minMaxValues; + }, + } +); +``` + +In this example, we use the `useQuery` hook from `react-query` to fetch faceting data from the server. Once the data is fetched, we set the `getGlobalFacetedUniqueValues` and `getGlobalFacetedMinMaxValues` table options to return the faceted values from the server response. This will allow the table to use the server-side faceting data for generating autocomplete suggestions and range filters. diff --git a/docs/guide/global-filtering.md b/docs/guide/global-filtering.md index ea71db3346..c23f5b18bf 100644 --- a/docs/guide/global-filtering.md +++ b/docs/guide/global-filtering.md @@ -6,14 +6,192 @@ title: Global Filtering Guide Want to skip to the implementation? Check out these examples: -- [filters-fuzzy](../../framework/react/examples/filters-fuzzy) +- [Global Filters](../../framework/react/examples/filters-global) ## API [Global Filtering API](../../api/features/global-filtering) -## Overview +## Global Filtering Guide Filtering comes in 2 flavors: Column Filtering and Global Filtering. This guide will focus on global filtering, which is a filter that is applied across all columns. + +### 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. + +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. + +### Manual Server-Side Global Filtering + +If you have decided that you need to implement server-side global filtering instead of using the built-in client-side global filtering, here's how you do that. + +No `getFilteredRowModel` table option is needed for manual server-side global filtering. Instead, the `data` that you pass to the table should already be filtered. However, if you have passed a `getFilteredRowModel` table option, you can tell the table to skip it by setting the `manualFiltering` option to `true`. + +```jsx +const table = useReactTable({ + data, + columns, + // getFilteredRowModel: getFilteredRowModel(), // not needed for manual server-side global filtering + manualFiltering: true, +}) +``` + +Note: When using manual global filtering, many of the options that are discussed in the rest of this guide will have no effect. When manualFiltering is set to true, the table instance will not apply any global filtering logic to the rows that are passed to it. Instead, it will assume that the rows are already filtered and will use the data that you pass to it as-is. + +### Client-Side Global Filtering + +If you are using the built-in client-side global filtering, first you need to pass in a getFilteredRowModel function to the table options. + +```jsx +import { useReactTable, getFilteredRowModel } from '@tanstack/react-table' +//... +const table = useReactTable({ + // other options... + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), // needed for client-side global filtering +}) +``` + +### Global Filter Function + +The globalFilterFn option allows you to specify the filter function that will be used for global filtering. The filter function can be a string that references a built-in filter function, a string that references a custom filter function provided via the tableOptions.filterFns option, or a custom filter function. + +```jsx +const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + globalFilterFn: 'text' // built-in filter function +}) +``` + +By default there are 10 built-in filter functions to choose from: + +- includesString - Case-insensitive string inclusion +- includesStringSensitive - Case-sensitive string inclusion +- equalsString - Case-insensitive string equality +- equalsStringSensitive - Case-sensitive string equality +- arrIncludes - Item inclusion within an array +- arrIncludesAll - All items included in an array +- arrIncludesSome - Some items included in an array +- equals - Object/referential equality Object.is/=== +- weakEquals - Weak object/referential equality == +- inNumberRange - Number range inclusion + +You can also define your own custom filter functions either as the globalFilterFn table option. + +### Global Filter State + +The global filter state is stored in the table's internal state and can be accessed via the table.getState().globalFilter property. If you want to persist the global filter state outside of the table, you can use the onGlobalFilterChange option to provide a callback function that will be called whenever the global filter state changes. + +```jsx +const [globalFilter, setGlobalFilter] = useState([]) + +const table = useReactTable({ + // other options... + state: { + globalFilter, + }, + onGlobalFilterChange: setGlobalFilte +}) +``` + +The global filtering state is defined as an object with the following shape: + +```jsx +interface GlobalFilter { + globalFilter: any +} +``` + +### Adding global filter input to UI + +TanStack table will not add a global filter input UI to your table. You should manually add it to your UI to allow users to filter the table. For example, you can add an input UI above the table to allow users to enter a search term. + +```jsx +return ( +
+ table.setGlobalFilter(String(e.target.value))} + placeholder="Search..." + /> +
+) +``` + +### Custom Global Filter Function + +If you want to use a custom global filter function, you can define the function and pass it to the globalFilterFn option. + +> **Note:** It is often a popular idea to use fuzzy filtering functions for global filtering. This is discussed in the [Fuzzy Filtering Guide](./fuzzy-filtering.md). + +```jsx +const customFilterFn = (rows, columnId, filterValue) => { + // custom filter logic +} + +const table = useReactTable({ + // other options... + globalFilterFn: customFilterFn +}) +``` + +### Initial Global Filter State + +If you want to set an initial global filter state when the table is initialized, you can pass the global filter state as part of the table initialState option. + +However, you can also just specify the initial global filter state in the state.globalFilter option. + +```jsx +const [globalFilter, setGlobalFilter] = useState("search term") //recommended to initialize globalFilter state here + +const table = useReactTable({ + // other options... + initialState: { + globalFilter: 'search term', // if not managing globalFilter state, set initial state here + } + state: { + globalFilter, // pass our managed globalFilter state to the table + } +}) +``` + +> NOTE: Do not use both initialState.globalFilter and state.globalFilter at the same time, as the initialized state in the state.globalFilter will override the initialState.globalFilter. + +### Disable Global Filtering + +By default, global filtering is enabled for all columns. You can disable the global filtering for all columns by using the enableGlobalFilter table option. You can also turn off both column and global filtering by setting the enableFilters table option to false. + +Disabling global filtering will cause the column.getCanGlobalFilter API to return false for that column. + +```jsx +const columns = [ + { + header: () => 'Id', + accessorKey: 'id', + enableGlobalFilter: false, // disable global filtering for this column + }, + //... +] +//... +const table = useReactTable({ + // other options... + columns, + enableGlobalFilter: false, // disable global filtering for all columns +}) +``` diff --git a/docs/guide/grouping.md b/docs/guide/grouping.md index 32c6bb9f3e..0bae282711 100644 --- a/docs/guide/grouping.md +++ b/docs/guide/grouping.md @@ -19,3 +19,129 @@ There are 3 table features that can reorder columns, which happen in the followi 1. [Column Pinning](../column-pinning) - If pinning, columns are split into left, center (unpinned), and right pinned columns. 2. Manual [Column Ordering](../column-ordering) - A manually specified column order is applied. 3. **Grouping** - If grouping is enabled, a grouping state is active, and `tableOptions.groupedColumnMode` is set to `'reorder' | 'remove'`, then the grouped columns are reordered to the start of the column flow. + +Grouping in TanStack table is a feature that applies to columns and allows you to categorize and organize the table rows based on specific columns. This can be useful in cases where you have a large amount of data and you want to group them together based on certain criteria. + +To use the grouping feature, you will need to use the grouped row model. This model is responsible for grouping the rows based on the grouping state. + +```tsx +import { getGroupedRowModel } from '@tanstack/react-table' + +const table = useReactTable({ + // other options... + getGroupedRowModel: getGroupedRowModel(), +}) +``` + +When grouping state is active, the table will add matching rows as subRows to the grouped row. The grouped row will be added to the table rows at the same index as the first matching row. The matching rows will be removed from the table rows. +To allow the user to expand and collapse the grouped rows, you can use the expanding feature. + +```tsx +import { getGroupedRowModel, getExpandedRowModel} from '@tanstack/react-table' + +const table = useReactTable({ + // other options... + getGroupedRowModel: getGroupedRowModel(), + getExpandedRowModel: getExpandedRowModel(), +}) +``` + +### Grouping state + +The grouping state is an array of strings, where each string is the ID of a column to group by. The order of the strings in the array determines the order of the grouping. For example, if the grouping state is ['column1', 'column2'], then the table will first group by column1, and then within each group, it will group by column2. You can control the grouping state using the setGrouping function: + +```tsx +table.setGrouping(['column1', 'column2']); +``` + +You can also reset the grouping state to its initial state using the resetGrouping function: + +```tsx +table.resetGrouping(); +``` + +By default, when a column is grouped, it is moved to the start of the table. You can control this behavior using the groupedColumnMode option. If you set it to 'reorder', then the grouped columns will be moved to the start of the table. If you set it to 'remove', then the grouped columns will be removed from the table. If you set it to false, then the grouped columns will not be moved or removed. + +```tsx +const table = useReactTable({ + // other options... + groupedColumnMode: 'reorder', +}) +``` + +### Aggregations + +When rows are grouped, you can aggregate the data in the grouped rows by columns using the aggregationFn option. This is a string that is the ID of the aggregation function. You can define the aggregation functions using the aggregationFns option. + +```tsx +const column = columnHelper.accessor('key', { + aggregationFn: 'sum', +}) +``` + +In the above example, the sum aggregation function will be used to aggregate the data in the grouped rows. +By default, numeric columns will use the sum aggregation function, and non-numeric columns will use the count aggregation function. You can override this behavior by specifying the aggregationFn option in the column definition. + +There are several built-in aggregation functions that you can use: + +- sum - Sums the values in the grouped rows. +- count - Counts the number of rows in the grouped rows. +- min - Finds the minimum value in the grouped rows. +- max - Finds the maximum value in the grouped rows. +- extent - Finds the extent (min and max) of the values in the grouped rows. +- mean - Finds the mean of the values in the grouped rows. +- median - Finds the median of the values in the grouped rows. +- unique - Returns an array of unique values in the grouped rows. +- uniqueCount - Counts the number of unique values in the grouped rows. + +#### Custom Aggregations + +When rows are grouped, you can aggregate the data in the grouped rows using the aggregationFns option. This is a record where the keys are the IDs of the aggregation functions, and the values are the aggregation functions themselves. You can then reference these aggregation functions in a column's aggregationFn option. + +```tsx +const table = useReactTable({ + // other options... + aggregationFns: { + myCustomAggregation: (columnId, leafRows, childRows) => { + // return the aggregated value + }, + }, +}) +``` + +In the above example, myCustomAggregation is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's aggregationFn option: + +```tsx +const column = columnHelper.accessor('key', { + aggregationFn: 'myCustomAggregation', +}) +``` + +### Manual Grouping + +If you are doing server-side grouping and aggregation, you can enable manual grouping using the manualGrouping option. When this option is set to true, the table will not automatically group rows using getGroupedRowModel() and instead will expect you to manually group the rows before passing them to the table. + +```tsx +const table = useReactTable({ + // other options... + manualGrouping: true, +}) +``` + +> **Note:** There are not currently many known easy ways to do server-side grouping with TanStack Table. You will need to do lots of custom cell rendering to make this work. + +### Grouping Change Handler + +If you want to manage the grouping state yourself, you can use the onGroupingChange option. This option is a function that is called when the grouping state changes. You can pass the managed state back to the table via the tableOptions.state.grouping option. + +```tsx +const [grouping, setGrouping] = useState([]) + +const table = useReactTable({ + // other options... + state: { + grouping: grouping, + }, + onGroupingChange: setGrouping +}) +```