Skip to content

Commit 976cd8f

Browse files
riccardoperracoderabbitai[bot]autofix-ci[bot]
authored
[WIP] feat: refactor reactivity feature with store override (#6182)
* feat: update @tanstack/store to v9 * Update packages/table-core/src/core/table/constructTable.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: lockfile * ci: apply automated fixes * feat: add solid table devtools * feat: add table devtools options panel * feat: move reactivity feature to core * feat: refactor reactivity feature with notifier impl * feat: try to refactor vue adapter * ci: apply automated fixes * wip vue adapter * wip options as store + some fixes in solid, angular adapter. fix react adapter * fix vue adapter * fix types * fix table devtools * fix lint * fix table devtools * fix knip issues --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent b5086d5 commit 976cd8f

36 files changed

Lines changed: 362 additions & 724 deletions

examples/angular/row-selection/src/app/app.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<div class="p-2">
2+
<button class="border rounded px-2" (click)="toggleEnableRowSelection()">
3+
{{ enableRowSelection() ? 'Disable' : 'Enable' }} Row selection
4+
</button>
5+
26
<div class="h-2"></div>
37

48
<table [tanStackTable]="table">

examples/angular/row-selection/src/app/app.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class App {
3232
private readonly rowSelection = signal<RowSelectionState>({})
3333
readonly globalFilter = signal<string>('')
3434
readonly data = signal(makeData(10_000))
35+
readonly enableRowSelection = signal(true)
3536

3637
readonly columns = columnHelper.columns([
3738
columnHelper.display({
@@ -93,7 +94,8 @@ export class App {
9394
state: {
9495
rowSelection: this.rowSelection(),
9596
},
96-
enableRowSelection: true, // enable row selection for all rows
97+
98+
enableRowSelection: this.enableRowSelection(), // enable row selection for all rows
9799
// enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
98100
onRowSelectionChange: (updaterOrValue) => {
99101
this.rowSelection.set(
@@ -136,4 +138,8 @@ export class App {
136138
refreshData(): void {
137139
this.data.set(makeData(10_000))
138140
}
141+
142+
toggleEnableRowSelection() {
143+
this.enableRowSelection.update((value) => !value)
144+
}
139145
}

examples/angular/row-selection/src/app/selection-column/selection-column.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export class TableHeaderSelection {
2929
<input
3030
type="checkbox"
3131
[checked]="context.row.getIsSelected()"
32+
[disabled]="!context.row.getCanSelect()"
3233
(change)="context.row.getToggleSelectedHandler()($event)"
3334
/>
3435
`,

examples/solid/row-selection/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
"vite-plugin-solid": "^2.11.10"
1818
},
1919
"dependencies": {
20-
"@tanstack/solid-table": "^9.0.0-alpha.10",
2120
"@tanstack/solid-devtools": "^0.7.26",
22-
"@tanstack/solid-table-devtools": "9.0.0-alpha.11",
21+
"@tanstack/solid-table": "^9.0.0-alpha.10",
22+
"@tanstack/solid-table-devtools": "workspace:*",
2323
"solid-js": "^1.9.11"
2424
}
2525
}

examples/solid/row-selection/src/App.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const _features = tableFeatures({
3333
function App() {
3434
const [data, setData] = createSignal(makeData(1_000))
3535
const refreshData = () => setData(makeData(100_000)) // stress test
36+
const [enableRowSelection, setEnableRowSelection] = createSignal(true)
3637

3738
// Create table first with a placeholder for columns
3839
let table: SolidTable<typeof _features, Person>
@@ -117,8 +118,6 @@ function App() {
117118
},
118119
]
119120

120-
const [enableRowSelection, setEnableRowSelection] = createSignal(true)
121-
122121
table = createTable({
123122
_features,
124123
_rowModels: {
@@ -136,8 +135,6 @@ function App() {
136135
debugTable: true,
137136
})
138137

139-
window.setEnable = setEnableRowSelection
140-
141138
return (
142139
// <table.Subscribe
143140
// selector={(state) => ({
@@ -336,6 +333,12 @@ function App() {
336333
>
337334
Log table.getSelectedRowModel().flatRows
338335
</button>
336+
<button
337+
class="border rounded p-2 mb-2"
338+
onClick={() => setEnableRowSelection((prev) => !prev)}
339+
>
340+
{enableRowSelection() ? 'Disable' : 'Enable'} Row Selection
341+
</button>
339342
</div>
340343
<div>
341344
<label>Row Selection State:</label>

examples/vue/row-selection/src/App.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,26 @@ const columns = columnHelper.columns([
8888
])
8989
9090
const data = ref(makeData(10))
91+
const enableRowSelection = ref(true)
9192
9293
const rerender = () => {
9394
data.value = makeData(10)
9495
}
9596
97+
const toggleRowSelection = () => {
98+
enableRowSelection.value = !enableRowSelection.value
99+
}
100+
96101
const table = useTable(
97102
{
98103
_features,
99104
_rowModels: {},
100105
data,
101106
columns,
102-
enableRowSelection: true, //enable row selection for all rows
107+
// enable row selection for all rows
108+
get enableRowSelection() {
109+
return enableRowSelection.value
110+
},
103111
// enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
104112
},
105113
(state) => ({ rowSelection: state.rowSelection }),
@@ -152,6 +160,9 @@ const table = useTable(
152160
</table>
153161
<div class="h-4" />
154162
<button @click="rerender" class="border p-2">Rerender</button>
163+
<button @click="toggleRowSelection" class="border p-2">
164+
{{ enableRowSelection ? 'Enable' : 'Disable' }} Row Selection
165+
</button>
155166
</div>
156167
</template>
157168

knip.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
{
22
"$schema": "https://unpkg.com/knip@5/schema.json",
33
"ignoreDependencies": ["@faker-js/faker"],
4-
"ignoreWorkspaces": ["examples/**", "packages/table-core/tests/**"],
4+
"ignoreWorkspaces": ["examples/**"],
55
"ignore": ["**/*benchmark*", "**/benchmarks/**"],
66
"workspaces": {
7+
"packages/table-core": {
8+
"ignore": ["**/tests/**"]
9+
},
710
"packages/match-sorter-utils": {
811
"ignoreDependencies": ["remove-accents"]
912
},

packages/angular-table/src/injectTable.ts

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import {
22
Injector,
33
assertInInjectionContext,
44
computed,
5+
effect,
56
inject,
6-
isSignal,
77
signal,
88
untracked,
99
} from '@angular/core'
@@ -31,6 +31,10 @@ export type AngularTable<
3131
* The selected state from the table store, based on the selector provided.
3232
*/
3333
readonly state: Signal<Readonly<TSelected>>
34+
/**
35+
* A signal that returns the entire table instance. Will update on table/options change.
36+
*/
37+
readonly value: Signal<AngularTable<TFeatures, TData, TSelected>>
3438
/**
3539
* Subscribe to changes in the table store with a custom selector.
3640
*/
@@ -107,18 +111,18 @@ export function injectTable<
107111
): AngularTable<TFeatures, TData, TSelected> {
108112
assertInInjectionContext(injectTable)
109113
const injector = inject(Injector)
110-
111-
const angularReactivityFeature = constructReactivityFeature({
112-
createSignal: (value) => {
113-
return signal(value) as any
114-
},
115-
createMemo: (fn) => {
116-
return computed(() => fn())
117-
},
118-
isSignal: (value) => isSignal(value),
119-
})
114+
const count = 0
120115

121116
return lazyInit(() => {
117+
const stateNotifier = signal(0)
118+
119+
const angularReactivityFeature = constructReactivityFeature({
120+
// optionsNotifier: () => stateNotifier(),
121+
stateNotifier: () => {
122+
return stateNotifier()
123+
},
124+
})
125+
122126
const resolvedOptions: TableOptions<TFeatures, TData> = {
123127
...options(),
124128
_features: {
@@ -127,14 +131,17 @@ export function injectTable<
127131
},
128132
} as TableOptions<TFeatures, TData>
129133

130-
const table: AngularTable<TFeatures, TData, TSelected> = constructTable(
131-
resolvedOptions,
132-
) as AngularTable<TFeatures, TData, TSelected>
134+
const table = constructTable(resolvedOptions) as AngularTable<
135+
TFeatures,
136+
TData,
137+
TSelected
138+
>
133139

134140
const updatedOptions = computed<TableOptions<TFeatures, TData>>(() => {
135141
const tableOptionsValue = options()
142+
const currentOptions = table.latestOptions
136143
const result: TableOptions<TFeatures, TData> = {
137-
...table.options,
144+
...currentOptions,
138145
...tableOptionsValue,
139146
_features: {
140147
...tableOptionsValue._features,
@@ -147,26 +154,34 @@ export function injectTable<
147154
return result
148155
})
149156

150-
const tableState = injectStore(
151-
table.store,
152-
(state: TableState<TFeatures>) => state,
157+
effect(
158+
() => {
159+
const newOptions = updatedOptions()
160+
untracked(() => table.setOptions(newOptions))
161+
},
153162
{ injector },
154163
)
155164

156-
const tableSignalNotifier = computed(
165+
const tableState = injectStore(table.store, (state) => state, { injector })
166+
const tableOptions = injectStore(table.optionsStore, (state) => state, {
167+
injector,
168+
})
169+
170+
let firstRun = true
171+
effect(
157172
() => {
173+
tableOptions()
158174
tableState()
159-
const newOptions = updatedOptions()
160-
untracked(() => table.setOptions(newOptions))
161-
untracked(() => table.baseStore.setState((prev) => ({ ...prev })))
162-
return table
175+
if (!firstRun) {
176+
untracked(() => {
177+
stateNotifier.update((n) => n + 1)
178+
})
179+
}
180+
firstRun = false
163181
},
164-
{ equal: () => false },
182+
{ injector },
165183
)
166184

167-
// @ts-ignore
168-
table.setTableNotifier(tableSignalNotifier)
169-
170185
table.Subscribe = function Subscribe<TSubSelected = {}>(props: {
171186
selector: (state: TableState<TFeatures>) => TSubSelected
172187
equal?: ValueEqualityFn<TSubSelected>
@@ -176,11 +191,17 @@ export function injectTable<
176191
equal: props.equal,
177192
})
178193
}
179-
180194
Object.defineProperty(table, 'state', {
181195
value: injectStore(table.store, selector, { injector }),
182196
})
183197

198+
Object.defineProperty(table, 'value', {
199+
value: computed(() => {
200+
stateNotifier()
201+
return table
202+
}),
203+
})
204+
184205
return table
185206
})
186207
}

packages/angular-table/tests/angularReactivityFeature.test.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('angularReactivityFeature', () => {
4444
const table = createTestTable()
4545
const tablePropertyKeys = Object.keys(table)
4646

47-
describe('Table property reactivity', () => {
47+
describe.skip('Table property reactivity', () => {
4848
test.each(
4949
tablePropertyKeys.map((property) => [
5050
property,
@@ -74,7 +74,7 @@ describe('angularReactivityFeature', () => {
7474
})
7575
})
7676

77-
describe('Header property reactivity', () => {
77+
describe.skip('Header property reactivity', () => {
7878
const headers = table.getHeaderGroups()
7979
headers.forEach((headerGroup, index) => {
8080
const headerPropertyKeys = Object.keys(headerGroup)
@@ -112,7 +112,7 @@ describe('angularReactivityFeature', () => {
112112
})
113113
})
114114

115-
describe('Column property reactivity', () => {
115+
describe.skip('Column property reactivity', () => {
116116
const columns = table.getAllColumns()
117117
columns.forEach((column, index) => {
118118
const columnPropertyKeys = Object.keys(column).concat(
@@ -133,7 +133,7 @@ describe('angularReactivityFeature', () => {
133133
})
134134
})
135135

136-
describe('Row and cells property reactivity', () => {
136+
describe.skip('Row and cells property reactivity', () => {
137137
const flatRows = table.getRowModel().flatRows
138138
flatRows.forEach((row, index) => {
139139
const rowsPropertyKeys = Object.keys(row).concat(
@@ -174,11 +174,12 @@ describe('angularReactivityFeature', () => {
174174
})
175175

176176
describe('Integration', () => {
177-
test('methods works will be reactive effects', () => {
177+
test('methods within effect will be re-trigger when options/state changes', () => {
178178
const data = signal<Array<Data>>([{ id: '1', title: 'Title' }])
179179
const table = createTestTable(data)
180180
const isSelectedRow1Captor = vi.fn<(val: boolean) => void>()
181181
const cellGetValueCaptor = vi.fn<(val: unknown) => void>()
182+
const cellGetValueMemoizedCaptor = vi.fn<(val: unknown) => void>()
182183
const columnIsVisibleCaptor = vi.fn<(val: boolean) => void>()
183184

184185
// This will test a case where you put in the effect a single cell property method
@@ -188,46 +189,58 @@ describe('angularReactivityFeature', () => {
188189
() => table.getRowModel().rows[0]!.getAllCells()[0]!,
189190
)
190191

192+
const cellGetValue = computed(() => cell().getValue())
193+
191194
TestBed.runInInjectionContext(() => {
192195
effect(() => {
193196
isSelectedRow1Captor(cell().row.getIsSelected())
194197
})
195198
effect(() => {
196199
cellGetValueCaptor(cell().getValue())
197200
})
201+
effect(() => {
202+
cellGetValueMemoizedCaptor(cellGetValue())
203+
})
198204
effect(() => {
199205
columnIsVisibleCaptor(cell().column.getIsVisible())
200206
})
201207
})
202208

203209
TestBed.tick()
204210
expect(isSelectedRow1Captor).toHaveBeenCalledTimes(1)
211+
expect(cellGetValueMemoizedCaptor).toHaveBeenCalledTimes(1)
205212
expect(cellGetValueCaptor).toHaveBeenCalledTimes(1)
206213
expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(1)
207214

208215
cell().row.toggleSelected(true)
209216
TestBed.tick()
210217
expect(isSelectedRow1Captor).toHaveBeenCalledTimes(2)
211218
expect(cellGetValueCaptor).toHaveBeenCalledTimes(1)
212-
expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(1)
219+
expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(2)
213220

214221
data.set([{ id: '1', title: 'Title 3' }])
215222
TestBed.tick()
216223
// In this case it will be called twice since `data` will change and
217224
// the cell instance will be recreated
218225
expect(isSelectedRow1Captor).toHaveBeenCalledTimes(3)
219226
expect(cellGetValueCaptor).toHaveBeenCalledTimes(2)
220-
expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(2)
227+
expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(3)
221228

222229
cell().column.toggleVisibility(false)
223230
TestBed.tick()
224-
expect(isSelectedRow1Captor).toHaveBeenCalledTimes(3)
231+
expect(isSelectedRow1Captor).toHaveBeenCalledTimes(4)
225232
expect(cellGetValueCaptor).toHaveBeenCalledTimes(2)
226-
expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(3)
233+
expect(columnIsVisibleCaptor).toHaveBeenCalledTimes(4)
227234

228-
expect(isSelectedRow1Captor.mock.calls).toEqual([[false], [true], [true]])
235+
expect(isSelectedRow1Captor.mock.calls).toEqual([
236+
[false],
237+
[true],
238+
[true],
239+
[true],
240+
])
229241
expect(cellGetValueCaptor.mock.calls).toEqual([['1'], ['1']])
230242
expect(columnIsVisibleCaptor.mock.calls).toEqual([
243+
[true],
231244
[true],
232245
[true],
233246
[false],

0 commit comments

Comments
 (0)