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
12 changes: 9 additions & 3 deletions packages/table-core/src/core/table/constructTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,15 @@ export function constructTable<
const controlledState = options.state as
Record<string, unknown> | undefined

return controlledState && hasOwn(controlledState, key)
? controlledState[key]
: reactiveState
if (controlledState && hasOwn(controlledState, key)) {
// An explicitly `undefined` controlled slice falls back to the
// slice's initial state so required snapshot slices stay defined.
const controlledValue = controlledState[key]
return controlledValue === undefined
? table.initialState[key]
: controlledValue
}
return reactiveState
},
{ debugName: `table/atoms/${key}` },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ export function table_syncExternalStateToBaseAtoms<
continue
}

const externalState = state[key as keyof typeof state]
// An explicitly `undefined` controlled slice syncs the slice's
// initial state instead so the base atom never holds `undefined`.
const rawExternalState = state[key as keyof typeof state]
const externalState =
rawExternalState === undefined
? (table.initialState as Record<string, unknown>)[key]
: rawExternalState
const currentState = table._reactivity.untrack(() => baseAtom.get())
if (!compare(currentState, externalState)) {
baseAtom.set(() => externalState)
Expand Down
67 changes: 67 additions & 0 deletions packages/table-core/tests/unit/core/tableAtoms.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { describe, expect, it, vi } from 'vitest'
import { batch, createAtom } from '@tanstack/store'
import {
columnFilteringFeature,
constructTable,
globalFilteringFeature,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
Expand Down Expand Up @@ -180,6 +182,71 @@ describe('three-layer atom architecture', () => {
})
})

describe('explicitly undefined controlled state (#5909)', () => {
it('falls back to the slice initial default at construct', () => {
const table = makeTable({ state: { sorting: undefined } })
expect(table.atoms.sorting.get()).toEqual([])
expect(table.store.state.sorting).toEqual([])
expect(table.baseAtoms.sorting.get()).toEqual([])
})

it('falls back to user-provided initialState', () => {
const table = makeTable({
initialState: { sorting: [{ id: 'name', desc: true }] },
state: { sorting: undefined },
})
expect(table.store.state.sorting).toEqual([{ id: 'name', desc: true }])
})

it('does not poison baseAtoms when a controlled slice becomes undefined', () => {
const controlled: SortingState = [{ id: 'name', desc: false }]
const table = makeTable({ state: { sorting: controlled } })
const internalTable = table as unknown as Table_Internal<
typeof features,
any
>
expect(table.baseAtoms.sorting.get()).toBe(controlled)

table_setOptions(internalTable, (options) => ({
...options,
state: { sorting: undefined },
}))
expect(table.store.state.sorting).toEqual([])
expect(table.baseAtoms.sorting.get()).toEqual([])

// removing the key returns the slice to uncontrolled internal writes
table_setOptions(internalTable, (options) => ({
...options,
state: {},
}))
table.setSorting([{ id: 'age', desc: true }])
expect(table.store.state.sorting).toEqual([{ id: 'age', desc: true }])
})

it('controlled globalFilter can still be cleared with undefined', () => {
const gfFeatures = testFeatures({
columnFilteringFeature,
globalFilteringFeature,
})
const table = constructTable({
features: gfFeatures,
columns: [],
data: [],
state: { globalFilter: 'search' },
})
expect(table.store.state.globalFilter).toBe('search')

table_setOptions(
table as unknown as Table_Internal<typeof gfFeatures, any>,
(options) => ({
...options,
state: { globalFilter: undefined },
}),
)
expect(table.store.state.globalFilter).toBeUndefined()
})
})

describe('store (readonly flat derived)', () => {
it('has identical public shape to TableState', () => {
const table = makeTable()
Expand Down
Loading