Config and State
This page replaces TanStack Table's Table Instance Guide. That page is
about the table object returned by useTable, createTable, or
injectTable: the one place that holds the options, the state, the APIs, and
the row models, and the thing every render function reaches back into.
There is no table instance here. Nothing is constructed, nothing is held, and no function reaches into a shared object. In its place are three values you pass around explicitly.
The Three Values
| Value | What it holds | Where it lives |
|---|---|---|
Table.Config row |
The columns and every feature flag. Nothing that changes as the user works. | Built once, at the top level of a module. |
Table.State |
Every slice the user can change: sorting, filters, grouping, expansion, selection, pagination, column order, visibility, pinning, sizing, cell selection. | Your model. |
Table.RowModel row |
The processed rows: the row tree, the same rows flattened, and a lookup by id. | Computed from the two above plus your data. |
Almost every function in the package has the shape
Config row -> State -> ... -> a. Two tables therefore share nothing by
accident, and every transition is a value you can inspect, log, or store.
Your Data
Any type works, and it never enters the Config. It is passed to whichever
pipeline function you call, so the same Config can process two different
lists. See Data.
Your Columns
Column definitions are the first argument to Table.config. See
Column Definitions for the constructors and the full set
of per-column options.
Building the Config
Table.config takes the column list and fills in TanStack's default for every
flag. The with* builders pipe onto it:
config : Table.Config Person
config =
Table.config
[ Table.column "firstName" (.firstName >> Value.String)
|> Table.withHeader "First name"
, Table.column "department" (.department >> Value.String)
|> Table.withHeader "Department"
, Table.column "salary" (.salary >> Value.Number)
|> Table.withHeader "Salary"
]
|> Table.withGetRowId (\person _ _ -> person.id)
|> Table.withGlobalFilterFn FilterFn.includesString
Build it once at the top level. It holds no state, so there is nothing to
recompute, and putting it in view rebuilds the column tree on every frame.
A Config that depends on runtime data, a generated column list or a
server-reported row count, is built where that data arrives, in init or
update.
Config is a plain record, so any field without a builder is a record update:
noGlobalFilter : Table.Config Person
noGlobalFilter =
{ config | enableGlobalFilter = False, maxMultiSortColCount = 2 }
Later versions of the package may add fields. That breaks code which pattern matches on the record, and does not break a record update.
Config Builders
Twelve options have a builder. They exist for the options whose values are functions, where a builder reads better than a record update.
| Builder | Sets | Description |
|---|---|---|
withGetRowId |
getRowId |
Give rows stable ids from your own key. |
withSubRows |
getSubRows |
Reach a row's children in your data. |
withDefaultColumn |
defaultColumn |
Change the size, minimum, and maximum every column falls back to. |
withGlobalFilterFn |
globalFilterFn |
The filter function the global filter uses. |
withRowSelection |
enableRowSelection |
Decide per row whether it can be selected. |
withRowCanExpand |
getRowCanExpand |
Decide per row whether it can be expanded. |
withIsRowExpanded |
getIsRowExpanded |
Decide per row whether it is expanded, ignoring State.expanded. |
withCellSpanning |
enableCellSpanning |
Allow cell spanning table-wide. |
withCellSelection |
enableCellSelection |
Allow cell selection table-wide. |
withCellSelectionWhen |
cellSelectionFilter |
Decide per cell whether it can be selected. |
withCellRangeSelection |
enableCellRangeSelection |
Allow a selection to be extended into a range. |
withMultiCellRangeSelection |
enableMultiCellRangeSelection |
Allow more than one range. |
Config Options
Every field of Table.Config row, with the value Table.config gives it.
Core
| Option | Type | Default | Description |
|---|---|---|---|
columns |
List (Column row) |
The argument to Table.config |
The column tree. |
getRowId |
Maybe (row -> Int -> Maybe String -> String) |
Nothing |
Row id from the datum, its index among its siblings, and its parent's id. Nothing uses the index path: "0", "0.1". |
getSubRows |
row -> List row |
\_ -> [] |
A row's children. The default is a flat table. |
Manual (Server-Side) Flags
| Option | Type | Default | Description |
|---|---|---|---|
manualFiltering |
Bool |
False |
Skip the filtered stage. |
manualGrouping |
Bool |
False |
Skip the grouped stage. |
manualSorting |
Bool |
False |
Skip the sorted stage. |
manualExpanding |
Bool |
False |
Skip the expanded stage. |
manualPagination |
Bool |
False |
Skip the paginated stage. |
See Client-Side vs Server-Side.
Sorting
| Option | Type | Default | Description |
|---|---|---|---|
enableSorting |
Bool |
True |
Allow sorting anywhere in the table. |
enableMultiSort |
Bool |
True |
Allow more than one sorted column. |
maxMultiSortColCount |
Int |
9007199254740991 |
How many columns a multi-sort may hold. The default is Number.MAX_SAFE_INTEGER, which is no practical limit. |
enableSortingRemoval |
Bool |
True |
Let the sort cycle return to unsorted rather than stopping at descending. |
enableMultiRemove |
Bool |
True |
Let a column be removed from a multi-sort. |
sortDescFirst |
Maybe Bool |
Nothing |
Table-wide first sort direction. Nothing leaves it to the column, then to the data. |
Filtering
| Option | Type | Default | Description |
|---|---|---|---|
enableFilters |
Bool |
True |
Allow filtering at all, column and global. |
enableColumnFilters |
Bool |
True |
Allow per-column filters. |
enableGlobalFilter |
Bool |
True |
Allow the global filter. |
getColumnCanGlobalFilter |
Maybe (Column row -> Bool) |
Nothing |
Which columns the global filter searches. Nothing keeps a column whose first non-null value is a string or a number. |
globalFilterFn |
Maybe FilterFn |
Nothing |
The global filter's function. Nothing means auto-detect. |
filterFromLeafRows |
Bool |
False |
Filter leaf rows and keep their ancestors, instead of filtering parents first. |
maxLeafRowFilterDepth |
Int |
100 |
How deep filtering descends into sub-rows. |
See Column Filtering and Global Filtering.
Grouping and Expanding
| Option | Type | Default | Description |
|---|---|---|---|
enableGrouping |
Bool |
True |
Allow grouping. |
groupedColumnMode |
GroupedColumnMode |
Table.groupedColumnsReorder |
What happens to a column that is being grouped by: groupedColumnsReorder moves it to the front, groupedColumnsRemove drops it, groupedColumnsIgnore leaves it. |
enableExpanding |
Bool |
True |
Allow expanding. |
getRowCanExpand |
Maybe (Row row -> Bool) |
Nothing |
Override the "has sub-rows" rule per row. |
getIsRowExpanded |
Maybe (Row row -> Bool) |
Nothing |
Decide expansion from the row instead of from State.expanded. |
See Grouping, Aggregation, and Expanding.
Pagination
| Option | Type | Default | Description |
|---|---|---|---|
paginateExpandedRows |
Bool |
True |
Count expanded sub-rows towards the page size. False keeps a parent's expanded children on its page. |
pageCount |
Maybe Int |
Nothing |
A server-reported page count. Just -1 means unknown. |
rowCount |
Maybe Int |
Nothing |
A server-reported row count. |
See Pagination.
Row Selection and Row Pinning
| Option | Type | Default | Description |
|---|---|---|---|
enableRowSelection |
Row row -> Bool |
always True |
Whether a row can be selected. |
enableMultiRowSelection |
Row row -> Bool |
always True |
Whether selecting a row keeps the others selected. |
enableSubRowSelection |
Row row -> Bool |
always True |
Whether selecting a parent selects its children. |
enableRowPinning |
Row row -> Bool |
always True |
Whether a row can be pinned. |
keepPinnedRows |
Bool |
True |
Take pinned rows from the pre-pagination model, so a pinned row stays visible when it is not on the current page. False pins only rows of the current page. |
These four take a Row row rather than a Bool, because TanStack accepts
either a boolean or a per-row predicate; always True and always False cover
the boolean cases. See Row Selection and
Row Pinning.
Columns
| Option | Type | Default | Description |
|---|---|---|---|
enableColumnPinning |
Bool |
True |
Allow column pinning. |
enableHiding |
Bool |
True |
Allow columns to be hidden. |
defaultColumn |
SizeDefaults |
{ size = 150, minSize = 20, maxSize = 9007199254740991 } |
The sizes a column falls back to when it sets none. |
See Column Pinning, Column Visibility, and Column Sizing.
Cell Spanning and Cell Selection
| Option | Type | Default | Description |
|---|---|---|---|
enableCellSpanning |
Bool |
True |
Allow cells to span. False makes every span 1. |
enableCellSelection |
Bool |
True |
Allow cells to be selected. |
cellSelectionFilter |
Maybe (Cell -> Bool) |
Nothing |
A per-cell check applied on top of the table and column flags. |
enableCellRangeSelection |
Bool |
True |
Allow a selection to be extended into a rectangle. |
enableMultiCellRangeSelection |
Bool |
True |
Allow further rectangles to be added or subtracted. |
See Cell Spanning and Cell Selection.
State
Table.State is one record with thirteen slices, one per feature. It is yours:
you put it in your model, you pass it to every function, and you replace it
with what a transition returns. Nothing in the package holds a copy, so what
you store is the whole truth.
Table.initialState is every slice at
its default.
| Slice | Type | initialState |
Feature |
|---|---|---|---|
sorting |
List SortColumn |
[] |
Sorting. Each entry is { id : String, desc : Bool }, in priority order. |
columnFilters |
List ColumnFilter |
[] |
Column Filtering. Each entry is { id : String, value : Value }. |
globalFilter |
Value |
Value.Null |
Global Filtering. Null and String "" both mean no filter. |
grouping |
List String |
[] |
Grouping. Column ids, outermost first. |
expanded |
Expanded |
Table.expandedIds Set.empty |
Expanding. Either expandedIds with a set of row ids or Table.expandAll. |
rowSelection |
Set String |
Set.empty |
Row Selection. Selected row ids. |
pagination |
Pagination |
{ pageIndex = 0, pageSize = 10 } |
Pagination. |
columnOrder |
List String |
[] |
Column Ordering. Empty means the order in the Config. |
columnVisibility |
Dict String Bool |
Dict.empty |
Column Visibility. A missing key means visible. |
columnPinning |
ColumnPinning |
{ left = [], right = [] } |
Column Pinning. Column ids per edge. |
columnSizing |
Dict String Float |
Dict.empty |
Column Sizing. A missing key uses the column's own size. |
rowPinning |
RowPinning |
{ top = [], bottom = [] } |
Row Pinning. Row ids per edge. |
cellSelection |
List CellSelectionRange |
[] |
Cell Selection. Rectangles, applied in order. |
State is a plain record too, so starting somewhere other than the default is
a record update on initialState:
startingState : Table.State
startingState =
let
base : Table.State
base =
Table.initialState
in
{ base
| pagination = { pageIndex = 0, pageSize = 25 }
, sorting = [ { id = "salary", desc = True } ]
}
Transitions
Every state change is a function ending in State -> State, so it composes
with |> and returns a value you store. Table.toggleSort,
Table.setColumnFilter, Table.setPage, Table.toggleRowSelected, and the
rest are listed on the feature guides.
Three of them take a RowModel as well, because their answer depends on the
data: toggleSort needs the automatic first sort direction,
setColumnFilter needs to know whether the filter should auto-remove itself,
and toggleExpanded needs the row tree. Pass the model you already have.
Each feature also has a resetX, which returns that slice to the package's
default rather than to a starting state you chose. There is no instance
carrying your initialState around. To go back to your own starting point,
call the matching setX with it.
Queries
The functions you call while rendering all take the Config, usually the
State, and sometimes a RowModel:
Table.visibleLeafColumns : Config row -> State -> List (Column row)
Table.getIsSorted : State -> String -> Maybe SortDir
Table.getValue : Config row -> Row row -> String -> Value
They are the counterpart of TanStack's table.getVisibleLeafColumns(),
column.getIsSorted(), and cell.getValue(), with the receiver passed in
rather than captured. The full list is in the
Table module reference, and the API reference pages
group them the way TanStack's do:
Table API, Column API,
Row API, Cell API, and
Header API.
Row Models
Table.rowsFromList runs the whole
pipeline: core, filtered, grouped, sorted, expanded, paginated. Each stage is
also exposed on its own, so you can stop early or inspect what one stage did.
Nothing is memoized, so the row model is computed exactly when you call for it.
That makes it the third thing worth keeping in your model, next to the State:
type alias Model =
{ state : Table.State
, rowModel : Table.RowModel Person
}
Recompute it in update, when the state or the data changes, rather than in
view:
update : Msg -> Model -> Model
update msg model =
case msg of
SortBy columnId ->
recompute
(Table.toggleSort config
model.rowModel
columnId
{ desc = Nothing, multi = False }
model.state
)
GoToNextPage ->
recompute (Table.nextPage config model.state)
recompute : Table.State -> Model
recompute state =
{ state = state
, rowModel = Table.rowsFromList config state people
}
Row Models covers the stages themselves. Table State covers the ownership patterns: a table inside a larger model, two tables side by side, keeping the row model in step, and resetting a slice.