CtrlK
BlogDocsLog inGet started
Tessl Logo

table-state

Read, track, initialize, control, and reset TanStack Ember Table v9 state through Glimmer-reactive table APIs, baseAtoms, atoms, store, Ember createAtom, or @tracked state plus on*Change. Load for ownership precedence, updater callbacks, stale template state, options-thunk reactivity, or incorrect table.Subscribe/store.subscribe usage.

70

Quality

87%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

This skill builds on @tanstack/table-core#core and getting-started. Keep state internal unless another part of the application must read, persist, validate, or drive a slice.

State Surfaces

State exists only for registered features:

  • table.baseAtoms are the table's internal writable atoms.
  • table.atoms are readonly, ownership-aware atoms for each registered slice.
  • table.store.state exposes the current flat state.
  • Feature methods such as setSorting, setPageSize, and row.toggleSelected() are the preferred write path.

Ember's reactivity feature bridges these reads into Glimmer tracking. There is no table.Subscribe, no selector argument to useTable, and no need to subscribe during rendering. Read the relevant API inside a template-consumed getter:

get rows() {
  return this.table.getRowModel().rows
}

get pageIndex() {
  return this.table.atoms.pagination.get().pageIndex
}

table.store.subscribe is not the Ember rendering mechanism. The adapter's store is pull-based; Glimmer tracks individual property and atom reads.

features and atoms are construct-time inputs in this adapter. Create them once before useTable; rerunning the options thunk updates ordinary live options but does not swap the table's feature set or atom owners.

Choose One Owner Per Slice

For a slice such as pagination, choose one source of truth:

  1. Internal baseAtoms by default.
  2. initialState.pagination for an internally owned starting value.
  3. An external writable atom in atoms.pagination.
  4. A @tracked value in state.pagination, paired with onPaginationChange.

Read precedence is atoms[key] over state[key] over the internal baseAtoms[key]. Do not configure multiple owners merely as fallbacks.

Internal and Initial State

With no initialState, atoms, state, or on[State]Change, Table owns registered state internally. Use initialState when only the starting/reset value differs:

table = useTable(() => ({
  features,
  columns,
  data: this.data,
  initialState: {
    pagination: { pageIndex: 0, pageSize: 25 },
  },
}))

Changing initialState later does not reset existing state. Prefer feature reset methods such as resetPagination(); passing true requests that feature's blank/default state. Core table.reset() resets internal base atoms and is not the primary reset path for externally owned atoms.

External Ember Atoms

Use the adapter's re-exported createAtom when a slice should be shared or read directly outside Table. It satisfies the TanStack Store atom contract and its reads are Glimmer-reactive:

import {
  createAtom,
  useTable,
  type PaginationState,
} from '@tanstack/ember-table'

paginationAtom = createAtom<PaginationState>({
  pageIndex: 0,
  pageSize: 10,
})

table = useTable(() => ({
  features,
  columns,
  data: this.data,
  atoms: { pagination: this.paginationAtom },
}))

get pagination() {
  return this.paginationAtom.get()
}

Feature APIs write through the external atom, so do not add onPaginationChange for the same atom-owned slice. Write the external atom itself when using the low-level path.

Controlled Tracked State

Use state plus the matching callback when a @tracked property owns a slice. Resolve both raw values and updater functions:

import {
  createPaginatedRowModel,
  rowPaginationFeature,
  tableFeatures,
  type PaginationState,
} from '@tanstack/ember-table'

const features = tableFeatures({
  rowPaginationFeature,
  paginatedRowModel: createPaginatedRowModel(),
})

@tracked pagination: PaginationState = { pageIndex: 0, pageSize: 10 }

table = useTable(() => ({
  features,
  columns,
  data: this.data,
  state: { pagination: this.pagination },
  onPaginationChange: (updater) => {
    this.pagination =
      typeof updater === 'function' ? updater(this.pagination) : updater
  },
}))

The pagination feature owns the state and APIs; the row-model slot enables client-side pagination. For server-owned pagination, keep rowPaginationFeature, omit the client row-model slot, and configure the matching manual/server data options. The options thunk must read this.pagination; assigning the tracked field reruns the thunk and feeds the controlled value back into Table. V9 has no global onStateChange option.

Low-Level Writes and Types

Use a feature method first. If internally owned state truly needs a low-level update:

this.table.baseAtoms.pagination.set((old) => ({ ...old, pageIndex: 0 }))

Do not write table.atoms.pagination; it is readonly and may point at an external owner. Infer the complete registered shape with TableState<typeof features> and use feature-specific types such as PaginationState or SortingState for owned fields.

Common Mistakes

CRITICAL Controlling a slice without writing callbacks back

Wrong:

state: { pagination: this.pagination },
onPaginationChange: () => {},

Correct: apply the value-or-updater to the tracked owner. Otherwise the slice is frozen at the controlled value.

HIGH Expecting a snapshot read or subscription component

Wrong: invent <table.Subscribe>, call table.store.subscribe(...) for rendering, or cache table.store.state.pagination outside a tracked getter.

Correct: read table.atoms.pagination.get(), table.store.state.pagination, or the relevant Table API inside a template-consumed getter.

table.getState() is removed v8 API, not an Ember state-read alternative.

HIGH Mixing atom and state ownership

If both atoms.pagination and state.pagination are present, the atom wins. Choose one owner so resets and writes have an unambiguous destination.

HIGH Mutating controlled objects in place

Wrong: this.pagination.pageIndex++.

Correct: assign a new tracked value or let table.setPageIndex(...) invoke the controlled updater. Glimmer and the options thunk need an observable owner update.

MEDIUM Writing the internal base atom for an external owner

When atoms.pagination owns the slice, changing baseAtoms.pagination does not replace the active value. Write the external atom or use the feature API.

API Discovery

Inspect node_modules/@tanstack/ember-table/declarations/use-table.d.ts for option/state precedence and the Glimmer bridge, signal.d.ts for createAtom behavior, and reactivity.d.ts for scheduling/tracking. Inspect the registered feature under node_modules/@tanstack/table-core/dist/features/<feature>/ for its state type, update API, and reset semantics.

Repository
TanStack/table
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.