Skip to content

feat: enable support for native reactivity in solid vue adapters#6298

Open
riccardoperra wants to merge 4 commits into
betafrom
feat/native-reactivity-solid-vue-frameworks
Open

feat: enable support for native reactivity in solid vue adapters#6298
riccardoperra wants to merge 4 commits into
betafrom
feat/native-reactivity-solid-vue-frameworks

Conversation

@riccardoperra
Copy link
Copy Markdown
Collaborator

@riccardoperra riccardoperra commented Jun 6, 2026

Updated Vue and Solid table reactivity to rely on framework-native primitives and table.atoms instead of the framework-specific TanStack Store adapters.

Summary:

  • Removed @tanstack/vue-store and @tanstack/solid-store from the Vue/Solid table adapters.
  • Switched Vue/Solid reactivity bindings to native refs/signals/memos while keeping core atom/store compatibility through @tanstack/store.
  • Removed the adapter-level table.state convenience API from Angular, Vue, and Solid. Updated examples to read state through table.store.get() / atoms instead of table.state.
  • Simplified Vue/Solid table.Subscribe so it no longer accepts source or selector; it now exposes table.atoms to the child callback, letting the framework track only the atom reads used by that render boundary.
  • Removed selector-based Subscribe plumbing and unused selector helpers.

  • table.atoms now provides the granular subscription model directly.
  • This avoids subscribing through full-table selectors and makes reactive reads explicit
  • . Vue and Solid can rely on their own reactive primitives for memoization/tracking, reducing adapter dependencies and simplifying the API.

Summary by CodeRabbit

  • Refactor

    • Updated examples across Angular, Solid, and Vue frameworks to read table state from internal store instead of public state property.
    • Simplified Solid-table and Vue-table subscription APIs by removing selector-based state selection and consolidating Subscribe to provide direct atom access.
  • Chores

    • Unified framework-specific store dependencies to use @tanstack/store package for Solid and Vue adapters.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 6, 2026

PR changed again? Review this PR in Change Stack to compare snapshots and stay oriented.

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afee4b06-45b7-4b31-a64e-8bd3bc08b924

📥 Commits

Reviewing files that changed from the base of the PR and between 5f299d1 and b53bbf6.

📒 Files selected for processing (26)
  • examples/angular/basic-external-atoms/src/app/app.ts
  • examples/angular/basic-external-state/src/app/app.ts
  • examples/angular/column-groups/src/app/app.ts
  • examples/angular/column-ordering/src/app/app.ts
  • examples/angular/column-pinning-split/src/app/app.ts
  • examples/angular/column-pinning-sticky/src/app/app.ts
  • examples/angular/column-pinning/src/app/app.ts
  • examples/angular/column-resizing-performant/src/app/app.ts
  • examples/angular/column-resizing/src/app/app.ts
  • examples/angular/column-sizing/src/app/app.ts
  • examples/angular/column-visibility/src/app/app.ts
  • examples/angular/composable-tables/src/app/components/table-components.ts
  • examples/angular/expanding/src/app/app.ts
  • examples/angular/filters-faceted/src/app/app.ts
  • examples/angular/filters-fuzzy/src/app/app.ts
  • examples/angular/filters/src/app/app.ts
  • examples/angular/grouping/src/app/app.ts
  • examples/angular/kitchen-sink/src/app/app.ts
  • examples/angular/pagination/src/app/app.ts
  • examples/angular/row-pinning/src/app/app.ts
  • examples/angular/row-selection-signal/src/app/app.component.ts
  • examples/angular/row-selection/src/app/app.ts
  • examples/angular/sorting/src/app/app.ts
  • examples/angular/virtualized-rows/src/app/app.ts
  • examples/angular/with-tanstack-form/src/app/app.ts
  • examples/angular/with-tanstack-query/src/app/app.ts
✅ Files skipped from review due to trivial changes (1)
  • examples/angular/row-pinning/src/app/app.ts
🚧 Files skipped from review as they are similar to previous changes (21)
  • examples/angular/filters/src/app/app.ts
  • examples/angular/column-resizing-performant/src/app/app.ts
  • examples/angular/column-pinning-split/src/app/app.ts
  • examples/angular/column-visibility/src/app/app.ts
  • examples/angular/column-groups/src/app/app.ts
  • examples/angular/basic-external-atoms/src/app/app.ts
  • examples/angular/sorting/src/app/app.ts
  • examples/angular/column-resizing/src/app/app.ts
  • examples/angular/column-pinning/src/app/app.ts
  • examples/angular/filters-faceted/src/app/app.ts
  • examples/angular/virtualized-rows/src/app/app.ts
  • examples/angular/pagination/src/app/app.ts
  • examples/angular/row-selection/src/app/app.ts
  • examples/angular/expanding/src/app/app.ts
  • examples/angular/composable-tables/src/app/components/table-components.ts
  • examples/angular/with-tanstack-form/src/app/app.ts
  • examples/angular/row-selection-signal/src/app/app.component.ts
  • examples/angular/with-tanstack-query/src/app/app.ts
  • examples/angular/grouping/src/app/app.ts
  • examples/angular/filters-fuzzy/src/app/app.ts
  • examples/angular/column-sizing/src/app/app.ts

📝 Walkthrough

Walkthrough

This PR replaces example and UI reads of table.state with table.store.get(), and simplifies Solid and Vue adapter APIs to provide table.atoms via a single Subscribe callback while removing selector-based selected-state flows.

Changes

Example state access updates

Layer / File(s) Summary
Examples: read state from store
examples/*/*/src/*
Many Angular, Solid, Vue example files updated to read pagination, filters, sorting, column sizing, and debug dumps from table.store.get() (or table().store.get()/table.store.get()) instead of .state/.state().

Core adapter and API changes

Layer / File(s) Summary
Angular adapter & tests
packages/angular-table/src/injectTable.ts, packages/angular-table/tests/angularReactivityFeature.test.ts
Removed runtime table.state proxy wiring; tests updated to read from table.atoms.*.get() and table.store.get() rather than table.state.
Solid adapter & hooks
packages/solid-table/src/createTable.ts, packages/solid-table/src/createTableHook.tsx, packages/solid-table/src/reactivity.ts, packages/solid-table/package.json, packages/solid-table/tests/unit/reactivity.test.ts
Public API simplified: removed selector/selected-state generics and .state accessor; Subscribe now supplies table.atoms to children; reactivity switched to @tanstack/store and corresponding tests updated.
Vue adapter & hooks
packages/vue-table/src/useTable.ts, packages/vue-table/src/createTableHook.ts, packages/vue-table/src/reactivity.ts, packages/vue-table/package.json, packages/vue-table/tests/unit/signals.test.ts
Removed selector-based Subscribe overloads and table.state; useTable/createTableHook simplified to atoms-based Subscribe; reactivity bridging reimplemented to wrap Vue computed/watch into TanStack Store atoms and track subscriptions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • TanStack/table#6248: Related change to table state access patterns in Angular demos, updating serialized state output sources.

Suggested reviewers

  • schiller-manuel
  • KevinVandy

"🐇 In burrows soft where carrots grow,
I hopped through code with ears aglow.
I swapped the state to store so neat,
Atoms now hum — the examples greet.
Hooray! The tables sing in sync."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: enabling native reactivity support in Solid and Vue adapters by removing framework-specific store adapters and switching to native reactivity primitives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/native-reactivity-solid-vue-frameworks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@riccardoperra riccardoperra force-pushed the feat/native-reactivity-solid-vue-frameworks branch from 5f299d1 to 663abeb Compare June 6, 2026 08:25
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Jun 6, 2026

View your CI Pipeline Execution ↗ for commit 663abeb

Command Status Duration Result
nx affected --targets=test:eslint,test:sherif,t... ❌ Failed 2m 28s View ↗
nx run-many --targets=build --exclude=examples/** ✅ Succeeded <1s View ↗

💡 Dealing with memory or CPU issues? See memory and CPU details with the resource usage add-on ↗.


☁️ Nx Cloud last updated this comment at 2026-06-06 08:31:55 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 6, 2026

More templates

@tanstack/angular-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/angular-table@6298

@tanstack/angular-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/angular-table-devtools@6298

@tanstack/lit-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/lit-table@6298

@tanstack/match-sorter-utils

npm i https://pkg.pr.new/TanStack/table/@tanstack/match-sorter-utils@6298

@tanstack/preact-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/preact-table@6298

@tanstack/preact-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/preact-table-devtools@6298

@tanstack/react-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/react-table@6298

@tanstack/react-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/react-table-devtools@6298

@tanstack/solid-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/solid-table@6298

@tanstack/solid-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/solid-table-devtools@6298

@tanstack/svelte-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/svelte-table@6298

@tanstack/table-core

npm i https://pkg.pr.new/TanStack/table/@tanstack/table-core@6298

@tanstack/table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/table-devtools@6298

@tanstack/vue-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/vue-table@6298

@tanstack/vue-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/vue-table-devtools@6298

commit: b53bbf6

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
examples/vue/kitchen-sink/src/App.vue (1)

344-344: 💤 Low value

Multiple store.get() calls in template.

The template calls table.store.get() five times in different bindings (lines 344, 741, 751, 763, 786). If each call creates a new snapshot object, this could have minor performance implications. Consider whether these calls are properly cached or if a single reactive reference would be more efficient.

Also applies to: 741-741, 751-751, 763-763, 786-786

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/vue/kitchen-sink/src/App.vue` at line 344, The template repeatedly
calls table.store.get() (e.g., to read globalFilter) which may create multiple
snapshot objects; replace repeated calls by capturing the snapshot once and
exposing a single reactive/computed reference (e.g., in setup create const
storeSnapshot = computed(() => table.store.get()) or const store =
ref(table.store.get()) and update as needed) and then bind to
store.value.globalFilter (or store.globalFilter if computed) in the template;
update all occurrences that use table.store.get() (including reads of
globalFilter and the other bindings) to use this single cached reference to
avoid repeated snapshot creation.
examples/solid/grouping/src/App.tsx (1)

207-228: ⚡ Quick win

Consider memoizing repeated table.store.get() calls.

The pagination state is accessed three times via separate table.store.get() calls (lines 207, 217, 228). Following the pattern from examples/solid/composable-tables/src/components/table-components.tsx, consider creating a memo to access the pagination state once:

const pagination = createMemo(() => table.store.get().pagination)

Then use pagination().pageIndex and pagination().pageSize in the UI bindings. This improves both performance and code clarity.

♻️ Suggested refactor
 function App() {
   const [data, setData] = createSignal(makeData(10_000))
   const refreshData = () => setData(makeData(10_000))
   const stressTest = () => setData(makeData(200_000))

   const table = createAppTable(
     {
       columns,
       get data() {
         return data()
       },
       debugTable: true,
     },
     (state) => state,
   )

+  const pagination = createMemo(() => table.store.get().pagination)
+
   return (
     <div class="demo-root">
       ...
       <span class="inline-controls">
         <div>Page</div>
         <strong>
-          {(table.store.get().pagination.pageIndex + 1).toLocaleString()} of{' '}
+          {(pagination().pageIndex + 1).toLocaleString()} of{' '}
           {table.getPageCount().toLocaleString()}
         </strong>
       </span>
       <span class="inline-controls">
         | Go to page:
         <input
           type="number"
           min="1"
           max={table.getPageCount()}
-          value={table.store.get().pagination.pageIndex + 1}
+          value={pagination().pageIndex + 1}
           onInput={(e) => {
             const page = e.currentTarget.value
               ? Number(e.currentTarget.value) - 1
               : 0
             table.setPageIndex(page)
           }}
           class="page-size-input"
         />
       </span>
       <select
-        value={table.store.get().pagination.pageSize}
+        value={pagination().pageSize}
         onChange={(e) => table.setPageSize(Number(e.currentTarget.value))}
       >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/solid/grouping/src/App.tsx` around lines 207 - 228, Memoize the
repeated table.store.get().pagination access by creating a memo like const
pagination = createMemo(() => table.store.get().pagination) and then replace
uses of table.store.get().pagination.pageIndex and
table.store.get().pagination.pageSize with pagination().pageIndex and
pagination().pageSize; keep existing bindings that call table.getPageCount() and
table.setPageIndex(page) but use the memoized pagination for value, min/max and
display to avoid multiple table.store.get() calls.
examples/solid/with-tanstack-form/src/App.tsx (1)

310-331: ⚡ Quick win

Consider memoizing repeated table.store.get() calls.

The pagination state is accessed three times via separate table.store.get() calls (lines 310, 320, 331). Following the pattern from examples/solid/composable-tables/src/components/table-components.tsx, consider creating a memo:

const pagination = createMemo(() => table.store.get().pagination)

Then use pagination().pageIndex and pagination().pageSize in the pagination controls. This improves both performance and code clarity.

♻️ Suggested refactor
 function App() {
   // Initialize form with makeData
   const form = useAppForm(() => ({
     defaultValues: {
       data: makeData(100),
     },
     onSubmit: ({ value }: { value: FormData }) => {
       alert(
         `Submitted ${value.data.length} records!\n\nFirst record: ${JSON.stringify(value.data[0], null, 2)}`,
       )
     },
     validators: {
       onChange: formSchema,
     },
   }))

   // Create columns with form fields for editing
   // Use createMemo since columns depend on `form` (which is reactive)
   const columns = createMemo(() =>
     columnHelper.columns([
       // ... columns definition
     ]),
   )

   // Create table using form state as data source
   const table = createTable({
     _features,
     _rowModels: {
       filteredRowModel: createFilteredRowModel(filterFns),
       paginatedRowModel: createPaginatedRowModel(),
     },
     get columns() {
       return columns()
     },
     get data() {
       return form.state.values.data
     },
     debugTable: true,
   })

+  const pagination = createMemo(() => table.store.get().pagination)
+
   const refreshData = () => {
     form.reset({ data: makeData(100) })
   }
   
   // ... rest of component
   
   return (
     <div class="demo-root">
       ...
       <span class="inline-controls">
         <div>Page</div>
         <strong>
-          {(table.store.get().pagination.pageIndex + 1).toLocaleString()} of{' '}
+          {(pagination().pageIndex + 1).toLocaleString()} of{' '}
           {table.getPageCount().toLocaleString()}
         </strong>
       </span>
       <span class="inline-controls">
         | Go to page:
         <input
           type="number"
           min="1"
           max={table.getPageCount()}
-          value={table.store.get().pagination.pageIndex + 1}
+          value={pagination().pageIndex + 1}
           onInput={(e) => {
             const page = e.currentTarget.value
               ? Number(e.currentTarget.value) - 1
               : 0
             table.setPageIndex(page)
           }}
           class="page-size-input"
         />
       </span>
       <select
-        value={table.store.get().pagination.pageSize}
+        value={pagination().pageSize}
         onChange={(e) => {
           table.setPageSize(Number(e.currentTarget.value))
         }}
       >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/solid/with-tanstack-form/src/App.tsx` around lines 310 - 331,
Replace repeated calls to table.store.get() in the pagination controls by
creating a memoized accessor for pagination (e.g., const pagination =
createMemo(() => table.store.get().pagination) at the top of the component) and
then use pagination().pageIndex and pagination().pageSize wherever
pageIndex/pageSize are currently read (including the value prop, display text,
and the max prop), while keeping the existing table.setPageIndex(...) and
table.getPageCount() calls unchanged.
examples/solid/pagination/src/App.tsx (1)

149-170: ⚡ Quick win

Consider memoizing repeated table.store.get() calls.

The pagination state is accessed three times via separate table.store.get() calls (lines 149, 159, 170). Following the pattern from examples/solid/composable-tables/src/components/table-components.tsx, consider creating a memo:

const pagination = createMemo(() => table.store.get().pagination)

Then use pagination().pageIndex and pagination().pageSize throughout. This improves both performance and code clarity.

♻️ Suggested refactor
 function MyTable(props: {
   data: Array<Person>
   columns: ReturnType<typeof columnHelper.columns>
 }) {
   const table = createTable({
     _features,
     _rowModels: {
       paginatedRowModel: createPaginatedRowModel(),
     },
     columns: props.columns,
     get data() {
       return props.data
     },
     debugTable: true,
   })

+  const pagination = createMemo(() => table.store.get().pagination)
+
   return (
     <div class="demo-root">
       ...
       <span class="inline-controls">
         <div>Page</div>
         <strong>
-          {(table.store.get().pagination.pageIndex + 1).toLocaleString()} of{' '}
+          {(pagination().pageIndex + 1).toLocaleString()} of{' '}
           {table.getPageCount().toLocaleString()}
         </strong>
       </span>
       <span class="inline-controls">
         | Go to page:
         <input
           type="number"
           min="1"
           max={table.getPageCount()}
-          value={table.store.get().pagination.pageIndex + 1}
+          value={pagination().pageIndex + 1}
           onInput={(e) => {
             const page = e.currentTarget.value
               ? Number(e.currentTarget.value) - 1
               : 0
             table.setPageIndex(page)
           }}
           class="page-size-input"
         />
       </span>
       <select
-        value={table.store.get().pagination.pageSize}
+        value={pagination().pageSize}
         onChange={(e) => {
           table.setPageSize(Number(e.currentTarget.value))
         }}
       >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/solid/pagination/src/App.tsx` around lines 149 - 170, The code
repeatedly calls table.store.get() for pagination; create a memoized accessor
(e.g., const pagination = createMemo(() => table.store.get().pagination) as in
the composable-tables example) and replace uses of
table.store.get().pagination/pageIndex/pageSize with pagination().pageIndex and
pagination().pageSize; keep table.getPageCount() and table.setPageIndex(...)
unchanged and update the input value/onInput and select value to read from
pagination() to improve performance and clarity.
examples/solid/column-resizing/src/App.tsx (1)

100-100: Use table.atoms.columnResizing.get() for deltaOffset in Solid to avoid coarse dependency tracking
table.store.get() is backed by a Solid memo, so deltaOffset updates in "onEnd" mode should still propagate. However, table.store is computed from all table state slices (table.atoms[key].get()), so reading table.store.get().columnResizing.deltaOffset over-subscribes and can trigger unnecessary recomputation. Switch the read to the slice-level atom, e.g. table.atoms.columnResizing.get().deltaOffset ?? 0.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/solid/column-resizing/src/App.tsx` at line 100, The current code
reads deltaOffset via table.store.get().columnResizing.deltaOffset which
over-subscribes Solid's memo; change the read to the slice-level atom by using
table.atoms.columnResizing.get().deltaOffset ?? 0 instead. Locate the usage of
deltaOffset (the expression referencing table.store.get()) and replace it with a
call to table.atoms.columnResizing.get(), keeping the nullish fallback (?? 0) so
the value remains safe when undefined. Ensure you only change the read site and
do not modify other table.store or atom writes.
packages/solid-table/tests/unit/reactivity.test.ts (1)

70-88: ⚡ Quick win

Exercise a real Subscribe update here.

This only proves that table.atoms is passed through. It would still pass if the callback body was untracked and const pagination = atoms.pagination.get() returned a stale snapshot. Please drive a table state change here and assert the child recomputes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/solid-table/tests/unit/reactivity.test.ts` around lines 70 - 88, The
test currently only checks that table.atoms is passed to children but doesn't
exercise reactivity; modify the test to drive a real state change and assert the
child recomputes: inside the table.Subscribe children capture the initial value
via atoms.pagination.get() (or atoms.pagination()) and store it, then after
Subscribe invoke a state mutation (either call the table API to change
pagination like table.setPageIndex(...) or directly update the atom via
table.atoms.pagination.set(...) / atoms.pagination.set(...)) and assert the
received value updates to the new pagination value (and differs from the initial
snapshot). Ensure you reference table.Subscribe, table.atoms and
atoms.pagination.get()/set() in the test so the change triggers the Subscribe
child recomputation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/vue-table/src/createTableHook.ts`:
- Around line 423-429: The default slot call in AppTable's setup currently
invokes slots.default with no args which causes consumers using v-slot="{ state
}" to destructure undefined; update the slot invocation in the AppTable
component (where provide(TableContext, table) is used and slots.default is
called) to pass an empty object as the first parameter (e.g., call
slots.default?.({})) so destructuring in consumer templates is safe and remains
backward-compatible.

In `@packages/vue-table/src/reactivity.ts`:
- Around line 58-69: The vueReactivity() implementation creates a local
subscriptions Set and exposes unmount() but never wires it into Vue’s lifecycle;
update either vueReactivity() or useTable to attach cleanup to the current Vue
scope: in vueReactivity() import onScopeDispose (or onUnmounted) and call
onScopeDispose(() => { subscriptions.forEach(s=>s.unsubscribe());
subscriptions.clear(); }), keep exporting the existing unmount() for external
use (or if you prefer change useTable.ts to call _reactivity.unmount?.() inside
its own onScopeDispose), ensuring the symbols vueReactivity(), subscriptions,
and unmount are used so subscriptions are unsubscribed when the component scope
is destroyed.

---

Nitpick comments:
In `@examples/solid/column-resizing/src/App.tsx`:
- Line 100: The current code reads deltaOffset via
table.store.get().columnResizing.deltaOffset which over-subscribes Solid's memo;
change the read to the slice-level atom by using
table.atoms.columnResizing.get().deltaOffset ?? 0 instead. Locate the usage of
deltaOffset (the expression referencing table.store.get()) and replace it with a
call to table.atoms.columnResizing.get(), keeping the nullish fallback (?? 0) so
the value remains safe when undefined. Ensure you only change the read site and
do not modify other table.store or atom writes.

In `@examples/solid/grouping/src/App.tsx`:
- Around line 207-228: Memoize the repeated table.store.get().pagination access
by creating a memo like const pagination = createMemo(() =>
table.store.get().pagination) and then replace uses of
table.store.get().pagination.pageIndex and table.store.get().pagination.pageSize
with pagination().pageIndex and pagination().pageSize; keep existing bindings
that call table.getPageCount() and table.setPageIndex(page) but use the memoized
pagination for value, min/max and display to avoid multiple table.store.get()
calls.

In `@examples/solid/pagination/src/App.tsx`:
- Around line 149-170: The code repeatedly calls table.store.get() for
pagination; create a memoized accessor (e.g., const pagination = createMemo(()
=> table.store.get().pagination) as in the composable-tables example) and
replace uses of table.store.get().pagination/pageIndex/pageSize with
pagination().pageIndex and pagination().pageSize; keep table.getPageCount() and
table.setPageIndex(...) unchanged and update the input value/onInput and select
value to read from pagination() to improve performance and clarity.

In `@examples/solid/with-tanstack-form/src/App.tsx`:
- Around line 310-331: Replace repeated calls to table.store.get() in the
pagination controls by creating a memoized accessor for pagination (e.g., const
pagination = createMemo(() => table.store.get().pagination) at the top of the
component) and then use pagination().pageIndex and pagination().pageSize
wherever pageIndex/pageSize are currently read (including the value prop,
display text, and the max prop), while keeping the existing
table.setPageIndex(...) and table.getPageCount() calls unchanged.

In `@examples/vue/kitchen-sink/src/App.vue`:
- Line 344: The template repeatedly calls table.store.get() (e.g., to read
globalFilter) which may create multiple snapshot objects; replace repeated calls
by capturing the snapshot once and exposing a single reactive/computed reference
(e.g., in setup create const storeSnapshot = computed(() => table.store.get())
or const store = ref(table.store.get()) and update as needed) and then bind to
store.value.globalFilter (or store.globalFilter if computed) in the template;
update all occurrences that use table.store.get() (including reads of
globalFilter and the other bindings) to use this single cached reference to
avoid repeated snapshot creation.

In `@packages/solid-table/tests/unit/reactivity.test.ts`:
- Around line 70-88: The test currently only checks that table.atoms is passed
to children but doesn't exercise reactivity; modify the test to drive a real
state change and assert the child recomputes: inside the table.Subscribe
children capture the initial value via atoms.pagination.get() (or
atoms.pagination()) and store it, then after Subscribe invoke a state mutation
(either call the table API to change pagination like table.setPageIndex(...) or
directly update the atom via table.atoms.pagination.set(...) /
atoms.pagination.set(...)) and assert the received value updates to the new
pagination value (and differs from the initial snapshot). Ensure you reference
table.Subscribe, table.atoms and atoms.pagination.get()/set() in the test so the
change triggers the Subscribe child recomputation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d1548d4-6888-4db9-b6c5-3bb98203a401

📥 Commits

Reviewing files that changed from the base of the PR and between f906d2d and 5f299d1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (84)
  • examples/angular/basic-external-atoms/src/app/app.ts
  • examples/angular/basic-external-state/src/app/app.ts
  • examples/angular/column-groups/src/app/app.ts
  • examples/angular/column-ordering/src/app/app.ts
  • examples/angular/column-pinning-split/src/app/app.ts
  • examples/angular/column-pinning-sticky/src/app/app.ts
  • examples/angular/column-pinning/src/app/app.ts
  • examples/angular/column-resizing-performant/src/app/app.ts
  • examples/angular/column-resizing/src/app/app.ts
  • examples/angular/column-sizing/src/app/app.ts
  • examples/angular/column-visibility/src/app/app.ts
  • examples/angular/composable-tables/src/app/components/table-components.ts
  • examples/angular/expanding/src/app/app.ts
  • examples/angular/filters-faceted/src/app/app.ts
  • examples/angular/filters-fuzzy/src/app/app.ts
  • examples/angular/filters/src/app/app.ts
  • examples/angular/grouping/src/app/app.ts
  • examples/angular/kitchen-sink/src/app/app.ts
  • examples/angular/pagination/src/app/app.ts
  • examples/angular/row-pinning/src/app/app.ts
  • examples/angular/row-selection-signal/src/app/app.component.ts
  • examples/angular/row-selection/src/app/app.ts
  • examples/angular/sorting/src/app/app.ts
  • examples/angular/virtualized-rows/src/app/app.ts
  • examples/angular/with-tanstack-form/src/app/app.ts
  • examples/angular/with-tanstack-query/src/app/app.ts
  • examples/solid/basic-external-atoms/src/App.tsx
  • examples/solid/basic-external-state/src/App.tsx
  • examples/solid/column-ordering/src/App.tsx
  • examples/solid/column-pinning-split/src/App.tsx
  • examples/solid/column-pinning-sticky/src/App.tsx
  • examples/solid/column-pinning/src/App.tsx
  • examples/solid/column-resizing-performant/src/App.tsx
  • examples/solid/column-resizing/src/App.tsx
  • examples/solid/column-sizing/src/App.tsx
  • examples/solid/column-visibility/src/App.tsx
  • examples/solid/composable-tables/src/components/table-components.tsx
  • examples/solid/expanding/src/App.tsx
  • examples/solid/filters-faceted/src/App.tsx
  • examples/solid/filters-fuzzy/src/App.tsx
  • examples/solid/filters/src/App.tsx
  • examples/solid/grouping/src/App.tsx
  • examples/solid/kitchen-sink/src/App.tsx
  • examples/solid/pagination/src/App.tsx
  • examples/solid/row-pinning/src/App.tsx
  • examples/solid/row-selection/src/App.tsx
  • examples/solid/sorting/src/App.tsx
  • examples/solid/virtualized-rows/src/App.tsx
  • examples/solid/with-tanstack-form/src/App.tsx
  • examples/solid/with-tanstack-query/src/App.tsx
  • examples/solid/with-tanstack-router/src/components/table.tsx
  • examples/vue/basic-external-atoms/src/App.tsx
  • examples/vue/basic-external-state/src/App.tsx
  • examples/vue/column-ordering/src/App.vue
  • examples/vue/column-pinning-split/src/App.vue
  • examples/vue/column-pinning-sticky/src/App.vue
  • examples/vue/column-pinning/src/App.vue
  • examples/vue/column-resizing-performant/src/App.vue
  • examples/vue/column-resizing/src/App.vue
  • examples/vue/column-sizing/src/App.vue
  • examples/vue/column-visibility/src/App.tsx
  • examples/vue/expanding/src/App.tsx
  • examples/vue/filters-faceted/src/App.vue
  • examples/vue/filters-fuzzy/src/App.vue
  • examples/vue/filters/src/App.vue
  • examples/vue/grouping/src/App.vue
  • examples/vue/kitchen-sink/src/App.vue
  • examples/vue/pagination/src/App.vue
  • examples/vue/row-pinning/src/App.vue
  • examples/vue/sorting/src/App.vue
  • examples/vue/with-tanstack-form/src/App.vue
  • examples/vue/with-tanstack-query/src/App.tsx
  • packages/angular-table/src/injectTable.ts
  • packages/angular-table/tests/angularReactivityFeature.test.ts
  • packages/solid-table/package.json
  • packages/solid-table/src/createTable.ts
  • packages/solid-table/src/createTableHook.tsx
  • packages/solid-table/src/reactivity.ts
  • packages/solid-table/tests/unit/reactivity.test.ts
  • packages/vue-table/package.json
  • packages/vue-table/src/createTableHook.ts
  • packages/vue-table/src/reactivity.ts
  • packages/vue-table/src/useTable.ts
  • packages/vue-table/tests/unit/signals.test.ts

Comment on lines 423 to 429
const AppTable = defineComponent({
name: 'AppTable',
props: {
selector: {
type: Function as PropType<(state: TableState<TFeatures>) => unknown>,
default: undefined,
},
},
setup(props, { slots }) {
setup(_, { slots }) {
provide(TableContext, table)
const selected = props.selector
? useSelector(table.store, props.selector)
: undefined

return () => {
if (!props.selector) {
return slots.default?.()
}

return slots.default?.({ state: selected?.value })
return slots.default?.()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass {} to the AppTable default slot.

Line 428 now calls the slot with no argument. Vue compiles v-slot="{ state }" to destructure the first parameter, so existing consumers will throw during render instead of just seeing state === undefined after this API removal.

Suggested fix
     const AppTable = defineComponent({
       name: 'AppTable',
       setup(_, { slots }) {
         provide(TableContext, table)
         return () => {
-          return slots.default?.()
+          return slots.default?.({})
         }
       },
     })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue-table/src/createTableHook.ts` around lines 423 - 429, The
default slot call in AppTable's setup currently invokes slots.default with no
args which causes consumers using v-slot="{ state }" to destructure undefined;
update the slot invocation in the AppTable component (where
provide(TableContext, table) is used and slots.default is called) to pass an
empty object as the first parameter (e.g., call slots.default?.({})) so
destructuring in consumer templates is safe and remains backward-compatible.

Comment on lines 58 to +69
export function vueReactivity(): TableReactivityBindings {
const subscriptions = new Set<Subscription>()

return {
createOptionsStore: true,
wrapExternalAtoms: false,
addSubscription: () => {
throw new Error(
'Feature not supported in current reactivity implementation',
)
wrapExternalAtoms: true,
addSubscription: (subscription) => {
subscriptions.add(subscription)
},
unmount: () => {
throw new Error(
'Feature not supported in current reactivity implementation',
)
subscriptions.forEach((s) => s.unsubscribe())
subscriptions.clear()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Reactivity binding contract / relevant symbols:"
rg -n -C2 'TableReactivityBindings|wrapExternalAtoms|addSubscription|unmount'

echo
echo "Vue lifecycle cleanup hooks still present in the adapter:"
rg -n -C2 'onScopeDispose|onUnmounted|getCurrentScope'

echo
echo "Call sites that invoke an unmount method:"
rg -n -C2 '\.unmount\s*\('

Repository: TanStack/table

Length of output: 36432


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Vue adapter teardown hooks (vue-table) =="

rg -n --hidden --no-ignore -S "onScopeDispose|getCurrentScope|onUnmounted|onBeforeUnmount" packages/vue-table -g'*.ts' -g'*.tsx' -g'*.vue'

echo
echo "== Call sites invoking unmount from reactivity bindings =="

rg -n --hidden --no-ignore -S "reactivity\.unmount\?\.|coreReativityFeature:|_reactivity\.unmount\?\." packages -g'*.ts' -g'*.tsx'

echo
echo "== Direct calls to unmount() in vue-table =="

rg -n --hidden --no-ignore -S "unmount\?\.\(\)|unmount\(" packages/vue-table -g'*.ts' -g'*.tsx'

echo
echo "== Check table-core reactivity binding invocation =="

rg -n --hidden --no-ignore -S "_reactivity\.unmount|\.unmount\?\." packages/table-core -g'*.ts'

echo
echo "== Inspect vue-table useTable implementation =="

ls -la packages/vue-table/src
rg -n "function useTable|export function useTable" packages/vue-table/src -g'*.ts' -g'*.tsx'

# Print relevant sections around useTable
FILE=$(rg -l "function useTable|export function useTable" packages/vue-table/src -g'*.ts' -g'*.tsx' | head -n 1)
echo "Using file: $FILE"
sed -n '1,220p' "$FILE" | nl -ba | head -n 220

Repository: TanStack/table

Length of output: 103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Vue adapter teardown hooks (vue-table) =="

rg -n --hidden --no-ignore -S "onScopeDispose|getCurrentScope|onUnmounted|onBeforeUnmount" packages/vue-table -g'*.ts' -g'*.tsx' -g'*.vue'

echo
echo "== Call sites invoking unmount from reactivity bindings =="

rg -n --hidden --no-ignore -S "reactivity\.unmount\?\.|_reactivity\.unmount\?\." packages -g'*.ts' -g'*.tsx'

echo
echo "== Direct calls to unmount in vue-table =="

rg -n --hidden --no-ignore -S "unmount\?\.\(\)|unmount\(" packages/vue-table -g'*.ts' -g'*.tsx'

echo
echo "== Table-core invocation of reactivity.unmount =="

rg -n --hidden --no-ignore -S "_reactivity\.unmount|\.unmount\?\." packages/table-core -g'*.ts'

echo
echo "== Inspect vue-table useTable implementation =="

ls -la packages/vue-table/src
FILE=$(rg -l "function useTable|export function useTable" packages/vue-table/src -g'*.ts' -g'*.tsx' | head -n 1)
echo "Using file: $FILE"
sed -n '1,260p' "$FILE" | nl -ba | head -n 260

Repository: TanStack/table

Length of output: 103


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "== Vue adapter teardown hooks (vue-table) =="
rg -n --hidden --no-ignore -S "onScopeDispose|getCurrentScope|onUnmounted|onBeforeUnmount" packages/vue-table -g'*.ts' -g'*.tsx' -g'*.vue'
echo "exit=$?"

echo
echo "== Call sites invoking unmount from reactivity bindings =="
rg -n --hidden --no-ignore -S "reactivity\.unmount\?\.|_reactivity\.unmount\?\." packages -g'*.ts' -g'*.tsx'
echo "exit=$?"

echo
echo "== Direct calls to unmount in vue-table =="
rg -n --hidden --no-ignore -S "unmount\?\.\(\)|unmount\(" packages/vue-table -g'*.ts' -g'*.tsx'
echo "exit=$?"

echo
echo "== Table-core invocation of reactivity.unmount =="
rg -n --hidden --no-ignore -S "_reactivity\.unmount|\.unmount\?\." packages/table-core -g'*.ts'
echo "exit=$?"

echo
echo "== Inspect vue-table useTable implementation =="
FILE=$(rg -l "function useTable|export function useTable" packages/vue-table/src -g'*.ts' -g'*.tsx' | head -n 1)
echo "Using file: $FILE"
if [ -n "${FILE:-}" ]; then
  sed -n '1,260p' "$FILE" | nl -ba
fi

Repository: TanStack/table

Length of output: 614


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search .unmount usage in vue-table =="
rg -n --hidden --no-ignore -S "\.unmount\s*\?\." packages/vue-table -g'*.ts' -g'*.tsx' -g'*.vue'
rg -n --hidden --no-ignore -S "\.unmount\s*\(" packages/vue-table -g'*.ts' -g'*.tsx' -g'*.vue'
rg -n --hidden --no-ignore -S "onScopeDispose|getCurrentScope|onUnmounted|onBeforeUnmount" packages/vue-table -g'*.ts' -g'*.tsx' -g'*.vue'
echo

FILE="packages/vue-table/src/useTable.ts"
echo "== Inspect $FILE (first 260 lines) =="
wc -l "$FILE"
sed -n '1,260p' "$FILE" | cat -n

echo
echo "== Inspect sections mentioning lifecycle/cleanup/unmount (whole file, filtered) =="
rg -n -S "unmount|onScopeDispose|getCurrentScope|onUnmounted|dispose|cleanup" "$FILE"

Repository: TanStack/table

Length of output: 99


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "== Search .unmount usage in vue-table =="
rg -n --hidden --no-ignore -S "\.unmount\s*\?\." packages/vue-table -g'*.ts' -g'*.tsx' -g'*.vue'
echo "exit_unmount_optional=$?"

rg -n --hidden --no-ignore -S "\.unmount\s*\(" packages/vue-table -g'*.ts' -g'*.tsx' -g'*.vue'
echo "exit_unmount_call=$?"

rg -n --hidden --no-ignore -S "onScopeDispose|getCurrentScope|onUnmounted|onBeforeUnmount" packages/vue-table -g'*.ts' -g'*.tsx' -g'*.vue'
echo "exit_vue_lifecycle=$?"

echo
FILE="packages/vue-table/src/useTable.ts"
echo "== Inspect $FILE =="
wc -l "$FILE"
sed -n '1,260p' "$FILE" | cat -n
echo
echo "== Filtered lines for cleanup/lifecycle =="
rg -n -S "unmount|onScopeDispose|getCurrentScope|onUnmounted|onBeforeUnmount|dispose|cleanup|stop\(" "$FILE"
echo "exit_filtered=$?"

Repository: TanStack/table

Length of output: 6631


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/vue-table/src/reactivity.ts"
echo "== $FILE =="
wc -l "$FILE"
echo
sed -n '1,120p' "$FILE" | cat -n
echo
echo "== vueReactivity() section =="
sed -n '40,120p' "$FILE" | cat -n

Repository: TanStack/table

Length of output: 5349


Wire vueReactivity() subscription cleanup to Vue teardown.

packages/vue-table/src/reactivity.ts keeps external atom subscriptions in a local subscriptions set and only clears them in unmount. That unmount isn’t connected to Vue lifecycle: vueReactivity() imports no onScopeDispose/onUnmounted, and packages/vue-table/src/useTable.ts contains no teardown hook or call to _reactivity.unmount?.(). Since table-core also doesn’t invoke it, wrapped external subscriptions can outlive the component.

Possible fix (attach cleanup to the current Vue scope)
-import { computed, shallowRef, watch } from 'vue'
+import { computed, getCurrentScope, onScopeDispose, shallowRef, watch } from 'vue'
 import { batch } from '`@tanstack/store`'
@@
 export function vueReactivity(): TableReactivityBindings {
   const subscriptions = new Set<Subscription>()
+
+  if (getCurrentScope()) {
+    onScopeDispose(() => {
+      subscriptions.forEach((s) => s.unsubscribe())
+      subscriptions.clear()
+    })
+  }
 
   return {
     createOptionsStore: true,
     wrapExternalAtoms: true,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue-table/src/reactivity.ts` around lines 58 - 69, The
vueReactivity() implementation creates a local subscriptions Set and exposes
unmount() but never wires it into Vue’s lifecycle; update either vueReactivity()
or useTable to attach cleanup to the current Vue scope: in vueReactivity()
import onScopeDispose (or onUnmounted) and call onScopeDispose(() => {
subscriptions.forEach(s=>s.unsubscribe()); subscriptions.clear(); }), keep
exporting the existing unmount() for external use (or if you prefer change
useTable.ts to call _reactivity.unmount?.() inside its own onScopeDispose),
ensuring the symbols vueReactivity(), subscriptions, and unmount are used so
subscriptions are unsubscribed when the component scope is destroyed.

type="number"
min="1"
[max]="table().getPageCount()"
[value]="table().state.pagination.pageIndex + 1"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should probably just be atom lookups, but fine either way

) => {
if (columnResizeMode() === 'onEnd' && header.column.getIsResizing()) {
const delta = table.state().columnResizing.deltaOffset ?? 0
const delta = table.store.get().columnResizing.deltaOffset ?? 0
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes more sense for atom read for this line since it's one slice of state

const table = useTableContext()

const pagination = createMemo(() => table.state().pagination)
const pagination = createMemo(() => table.store.get().pagination)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read pagination atom instead of store

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants