Skip to content

Conversation

@derekperkins
Copy link

@derekperkins derekperkins commented Nov 4, 2025

🎯 Changes

This enables framework agnostic table adapters to create a table outside of the react context, but then use the existing react package to interact with it as a regular table.

The second commit is AI matching the changes to useReactTable across all the other frameworks. I don't have the framework experience to know if it did a great job or not, but the changes should be pretty simple to review.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features
    • Enhanced useReactTable hook with an optional existingTable parameter. Developers can now optionally provide an externally-created Table instance for reuse instead of having the hook automatically create a new one, providing greater flexibility and control over table instance lifecycle management.

this enables framework agnostic table adapters to create a table outside of the react context, but then use the existing react package to interact with it as a regular table
@changeset-bot
Copy link

changeset-bot bot commented Nov 4, 2025

🦋 Changeset detected

Latest commit: e468632

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@tanstack/react-table Patch
@tanstack/react-table-devtools Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Nov 4, 2025

Walkthrough

Adds an optional existingTable parameter across multiple framework adapters (React, Angular, Lit, Qwik, Solid, Svelte, Vue) to allow reusing a provided Table instance instead of always creating a new one; a changeset documents the API addition.

Changes

Cohort / File(s) Change Summary
Changeset Entry
.changeset/beige-singers-admire.md
Patch notes added documenting the new existingTable parameter across adapters.
Framework adapters
packages/react-table/src/index.tsx, packages/angular-table/src/index.ts, packages/lit-table/src/index.ts, packages/qwik-table/src/index.tsx, packages/solid-table/src/index.tsx, packages/svelte-table/src/index.ts, packages/vue-table/src/index.ts
Each API now accepts an optional existingTable?: Table<TData> parameter (typed from @tanstack/table-core) and initializes the internal table with existingTable ?? createTable(resolvedOptions) (or equivalent), preserving prior reactive wiring while enabling table reuse.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant AdapterHook as Adapter Hook
  participant createTable as createTable
  note right of AdapterHook `#D6EAF8`: receives options, optional existingTable
  Caller->>AdapterHook: call(options, existingTable?)
  alt existingTable provided
    AdapterHook-->>Caller: return existingTable (wired/reactive)
  else no existingTable
    AdapterHook->>createTable: createTable(resolvedOptions)
    createTable-->>AdapterHook: new Table instance
    AdapterHook-->>Caller: return new Table (wired/reactive)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Multiple files across packages changed with the same pattern (parameter addition + conditional initialization).
  • Review focus areas:
    • Correct typing/import of Table from @tanstack/table-core.
    • Ensure no unintended double-initialization across frameworks (Qwik store initialization, Lit controller lifecycle, Svelte/Vue reactivity).
    • Verify exports/signatures and generated declarations if present.

Poem

🐰 I found a table, old and neat,
No need to craft another seat,
Pass it in, and off we run,
Reuse and rest — our work is done,
Hop, code, flourish — one table, one beat.

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding an existingTable parameter to useReactTable, though the scope extends beyond React to multiple framework adapters.
Description check ✅ Passed The description follows the template structure with all required sections completed, including rationale for the changes and proper checklist completion.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-table/src/index.tsx (1)

76-93: State initialization mismatch when reusing external tables.

When an existingTable is provided that has already been used (i.e., its current state differs from its initialState), line 76 initializes the React state using tableRef.current.initialState rather than the current state, potentially discarding prior modifications. This React state is then synced back to the table via setOptions (lines 80-93), causing unexpected state resets.

Example scenario:

  1. External table created with initialState: { pagination: { pageIndex: 0 } }
  2. Table used elsewhere, state updated to { pagination: { pageIndex: 5 } }
  3. Table passed to useReactTable as existingTable
  4. Hook initializes React state to initialState (pageIndex: 0) on line 76
  5. Hook syncs this stale state back to the table via setOptions
  6. Result: Page index unexpectedly resets from 5 to 0

Suggested fix:
Use the table's current state instead of its initial state:

-  const [state, setState] = React.useState(() => tableRef.current.initialState)
+  const [state, setState] = React.useState(() => tableRef.current.getState())

Verify this doesn't break state management for newly created tables and document the intended usage pattern for existingTable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02c203a and 90eddf6.

📒 Files selected for processing (2)
  • .changeset/beige-singers-admire.md (1 hunks)
  • packages/react-table/src/index.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • packages/react-table/src/index.tsx
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array using `subRows ?? []`, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • packages/react-table/src/index.tsx
🧬 Code graph analysis (1)
packages/react-table/src/index.tsx (2)
packages/table-core/src/types.ts (2)
  • TableOptions (173-177)
  • Table (137-152)
packages/table-core/src/core/table.ts (1)
  • createTable (283-527)
🔇 Additional comments (3)
packages/react-table/src/index.tsx (2)

9-9: LGTM: Table type import is necessary and correct.

The Table type import is required to type the new existingTable parameter and follows the existing import pattern from @tanstack/table-core.


70-73: LGTM: Table initialization logic is correct.

The conditional initialization using existingTable ?? createTable<TData>(resolvedOptions) correctly uses the provided table when available, falling back to creating a new one. The useState callback ensures this only executes once per component mount.

Note: resolvedOptions (lines 63-68) is computed regardless of whether existingTable is provided, but this is a minor inefficiency and doesn't affect correctness.

.changeset/beige-singers-admire.md (1)

1-5: LGTM: Changeset is correct.

The changeset correctly documents a patch version bump with an appropriate feature description. The format follows standard conventions.

Comment on lines 58 to 61
export function useReactTable<TData extends RowData>(
options: TableOptions<TData>
options: TableOptions<TData>,
existingTable?: Table<TData>
) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add JSDoc documentation for the new parameter.

The existingTable parameter introduces a significant new usage pattern but lacks documentation explaining when and how it should be used, what happens to the existing table's state, and any constraints or gotchas.

Add JSDoc to document the parameter:

+/**
+ * Hook to create or reuse a TanStack table instance within React.
+ * @param options - Table configuration options
+ * @param existingTable - Optional pre-created table instance. When provided, this table will be used instead of creating a new one. Note: The hook will manage state internally and sync it with the table via setOptions on each render.
+ */
 export function useReactTable<TData extends RowData>(
   options: TableOptions<TData>,
   existingTable?: Table<TData>
 ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function useReactTable<TData extends RowData>(
options: TableOptions<TData>
options: TableOptions<TData>,
existingTable?: Table<TData>
) {
/**
* Hook to create or reuse a TanStack table instance within React.
* @param options - Table configuration options
* @param existingTable - Optional pre-created table instance. When provided, this table will be used instead of creating a new one. Note: The hook will manage state internally and sync it with the table via setOptions on each render.
*/
export function useReactTable<TData extends RowData>(
options: TableOptions<TData>,
existingTable?: Table<TData>
) {
🤖 Prompt for AI Agents
In packages/react-table/src/index.tsx around lines 58 to 61, the new
existingTable parameter of useReactTable is undocumented; add a JSDoc block
above the function that documents the parameter with: a clear description of
when to pass an existing Table instance, what parts of the existing table’s
state (e.g., selectedRows, columnOrder, sorting, pagination) are adopted or left
untouched, whether the hook will mutate or replace the provided table (and if it
clones it), lifecycle/ownership expectations (caller retains responsibility for
cleanup), accepted types/nullability (Table<TData> | undefined), and any gotchas
or constraints (concurrent use, prop mismatch, incompatible options). Keep it
concise and include @param existingTable and a short @returns note if not
already present.

this enables framework agnostic table adapters to create a table outside of the framework context, but then use the existing packages to interact with it as a regular table
Copy link

@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: 1

🧹 Nitpick comments (5)
packages/svelte-table/src/index.ts (1)

71-74: Consider importing Table type at the top for consistency.

The inline type import import('@tanstack/table-core').Table<TData> works correctly but is inconsistent with the other imports from @tanstack/table-core at the top of the file. Consider adding Table to the imports on lines 1-6 for better consistency and readability.

Additionally, consider adding JSDoc to document the purpose and usage of the existingTable parameter, particularly noting that it should be a freshly created table instance that the adapter will manage.

Apply this diff to improve import consistency:

 import {
   RowData,
   createTable,
   TableOptions,
   TableOptionsResolved,
+  Table,
 } from '@tanstack/table-core'

And update the function signature:

 export function createSvelteTable<TData extends RowData>(
   options: ReadableOrVal<TableOptions<TData>>,
-  existingTable?: import('@tanstack/table-core').Table<TData>
+  existingTable?: Table<TData>
 ) {
packages/lit-table/src/index.ts (1)

39-48: Consider documenting the one-time initialization behavior.

The existingTable parameter is only used during the first call to table() for a given controller instance. Subsequent calls reuse the cached this.tableInstance. While this behavior seems correct, adding a JSDoc comment would help clarify this for API consumers.

Example documentation:

+  /**
+   * Get or initialize the table instance.
+   * @param options - Table configuration options
+   * @param existingTable - Optional pre-existing table instance (only used on first call)
+   * @returns The table instance
+   */
   public table(options: TableOptions<TData>, existingTable?: Table<TData>) {
packages/solid-table/src/index.tsx (1)

27-30: Implementation looks good; consider importing Table at the top for consistency.

The function signature change correctly adds an optional existingTable parameter, maintaining backward compatibility. The inline import syntax import('@tanstack/table-core').Table<TData> works correctly but differs from the conventional pattern used for other imports in this file.

If desired, you can import Table at the top for consistency:

 import {
   TableOptions,
   createTable,
   TableOptionsResolved,
   RowData,
+  Table,
 } from '@tanstack/table-core'

Then update the parameter type:

 export function createSolidTable<TData extends RowData>(
   options: TableOptions<TData>,
-  existingTable?: import('@tanstack/table-core').Table<TData>
+  existingTable?: Table<TData>
 ) {
packages/vue-table/src/index.ts (2)

54-55: Import Table type at the top for consistency.

The existingTable parameter uses an inline import type (import('@tanstack/table-core').Table<TData>), while other types from the same package are imported at the top of the file. For consistency and readability, add Table to the top-level imports.

Apply this diff to add the import:

 import {
   TableOptions,
   createTable,
   RowData,
   TableOptionsResolved,
+  Table,
 } from '@tanstack/table-core'

Then update the function signature:

 export function useVueTable<TData extends RowData>(
   initialOptions: TableOptionsWithReactiveData<TData>,
-  existingTable?: import('@tanstack/table-core').Table<TData>
+  existingTable?: Table<TData>
 ) {

53-56: Consider adding JSDoc to document the new parameter.

The new existingTable parameter lacks documentation. Adding a JSDoc comment would help users understand when and how to use this parameter, especially since it enables a framework-agnostic table adapter pattern.

Consider adding documentation like this:

+/**
+ * Creates a Vue-compatible table instance or wraps an existing table instance.
+ * 
+ * @param initialOptions - Table configuration options with Vue reactive data support
+ * @param existingTable - Optional pre-created table instance to wrap with Vue reactivity
+ * @returns A table instance with Vue reactive state management
+ */
 export function useVueTable<TData extends RowData>(
   initialOptions: TableOptionsWithReactiveData<TData>,
   existingTable?: import('@tanstack/table-core').Table<TData>
 ) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90eddf6 and e468632.

📒 Files selected for processing (6)
  • packages/angular-table/src/index.ts (2 hunks)
  • packages/lit-table/src/index.ts (2 hunks)
  • packages/qwik-table/src/index.tsx (2 hunks)
  • packages/solid-table/src/index.tsx (2 hunks)
  • packages/svelte-table/src/index.ts (2 hunks)
  • packages/vue-table/src/index.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array using `subRows ?? []`, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • packages/solid-table/src/index.tsx
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • packages/solid-table/src/index.tsx
🧬 Code graph analysis (6)
packages/angular-table/src/index.ts (3)
packages/table-core/src/types.ts (1)
  • TableOptions (173-177)
packages/lit-table/src/index.ts (1)
  • table (39-67)
packages/table-core/src/core/table.ts (1)
  • createTable (283-527)
packages/qwik-table/src/index.tsx (3)
packages/table-core/src/types.ts (1)
  • TableOptions (173-177)
packages/table-core/src/core/table.ts (1)
  • createTable (283-527)
packages/lit-table/src/index.ts (1)
  • table (39-67)
packages/lit-table/src/index.ts (2)
packages/table-core/src/types.ts (1)
  • TableOptions (173-177)
packages/table-core/src/core/table.ts (1)
  • createTable (283-527)
packages/solid-table/src/index.tsx (2)
packages/table-core/src/types.ts (1)
  • TableOptions (173-177)
packages/lit-table/src/index.ts (1)
  • table (39-67)
packages/svelte-table/src/index.ts (2)
packages/table-core/src/types.ts (1)
  • TableOptions (173-177)
packages/lit-table/src/index.ts (1)
  • table (39-67)
packages/vue-table/src/index.ts (2)
packages/lit-table/src/index.ts (1)
  • table (39-67)
packages/table-core/src/types.ts (1)
  • TableOptionsResolved (169-171)
🔇 Additional comments (13)
packages/svelte-table/src/index.ts (1)

90-90: Implementation looks good.

The nullish coalescing pattern correctly reuses the provided table or creates a new one. This matches the implementation in other adapters (e.g., lit-table) and aligns with the PR objective of allowing externally created table instances to be managed by the framework adapter.

Note that the adapter will take over state management of the provided table through the setOptions call below (lines 97-114), initializing its internal state from table.initialState.

packages/lit-table/src/index.ts (2)

39-39: LGTM! Signature change supports the new reuse pattern.

The optional existingTable parameter is correctly typed and maintains backward compatibility.

Please verify that this new parameter is tested, especially the scenario where an external table instance is provided and properly integrated with Lit's reactive update cycle.


48-48: Correct implementation of the existingTable fallback.

The nullish coalescing operator correctly uses the provided table or creates a new one. The setOptions call on line 55 ensures that options are properly merged and the Lit-specific onStateChange wrapper is applied regardless of table origin.

packages/solid-table/src/index.tsx (1)

46-47: Table creation and state initialization logic is correct.

The implementation correctly uses the existingTable when provided and falls back to creating a new table otherwise. The state initialization using table.initialState is appropriate for both cases and is consistent with the Lit implementation pattern shown in the relevant code snippets.

packages/vue-table/src/index.ts (1)

79-81: No issues found—behavior is consistent and intentional across all framework adapters.

The pattern of calling setState() and setOptions() after table initialization is consistent across Vue, React, Solid, Svelte, Angular, Qwik, and Lit implementations. This design allows reusing a table instance while synchronizing it with new reactive options and data—the entire purpose of the existingTable parameter. The code is correct as written.

packages/qwik-table/src/index.tsx (7)

34-35: LGTM! Clean API addition for table instance reuse.

The optional existingTable parameter follows the cross-framework pattern established in this PR and maintains backward compatibility.


46-50: Correct use of NoSerialize for Qwik's serialization model.

The useStore with NoSerialize<Table<TData>> is the appropriate pattern for Qwik, as the Table instance contains functions and other non-serializable data that should be excluded from Qwik's resumability serialization.


58-58: Non-null assertion is safe here.

The non-null assertion is justified because the previous initialization block (lines 52-56) guarantees tableStore.instance is set before this line executes.


61-61: LGTM! Correct state initialization.

The state initialization properly uses table.initialState from either the existing or newly created table instance, and correctly leverages Qwik's signal for reactivity.


65-76: LGTM! Proper options and state management.

The setOptions call correctly merges user options with internal state management, and the onStateChange handler properly updates the Qwik signal while respecting user-provided callbacks. This works correctly for both new and existing table instances.


78-78: LGTM! Clean return statement.

The return statement correctly provides the table instance, whether it was newly created or provided via existingTable.


52-56: This is intentional lazy initialization—no action needed.

The pattern where existingTable is only considered on the first render is consistent across all framework adapters (Vue, Svelte, Solid, Angular, Lit, React, and Qwik). The table instance is created once and persists thereafter, maintaining a stable reference across re-renders. This is the intended design.

packages/angular-table/src/index.ts (1)

40-43: State initialization with existingTable is by design but lacks documentation.

Your observation is correct: when existingTable is provided, line 43 initializes the state signal to table.initialState, which represents the state at table creation time. Any state modifications made to the existing table after its creation will be lost.

However, this pattern is intentional and consistent across all framework adapters (React, Vue, Svelte, Solid, Qwik, Lit). The design allows state to be preserved by passing it via the options.state parameter—line 56 merges tableOptions.state into the final state, enabling consumers to explicitly preserve existing state when reusing a table.

The underlying issue is not a code defect but a missing contract: without documentation or tests explaining this behavior, it's unclear that consumers must explicitly pass existing state through options.state when reusing a table instance. Consider:

  • Adding a test case that documents the expected behavior when existingTable is provided with modified state
  • Adding a JSDoc comment explaining the contract: "If reusing an existing table with modified state, pass that state via options.state"

Comment on lines 28 to 31
export function createAngularTable<TData extends RowData>(
options: () => TableOptions<TData>
options: () => TableOptions<TData>,
existingTable?: Table<TData>
): Table<TData> & Signal<Table<TData>> {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add JSDoc documentation for the new existingTable parameter.

The new existingTable parameter is a public API addition but lacks documentation explaining its purpose and intended usage pattern. Please add JSDoc comments describing when and how to use this parameter.

Apply this diff to add documentation:

+/**
+ * Creates an Angular-reactive table instance.
+ * @param options - A function returning table options
+ * @param existingTable - Optional existing table instance to reuse instead of creating a new one.
+ *                        Useful for framework-agnostic table adapters that create a table outside
+ *                        the Angular context and then wire it up with Angular's reactivity system.
+ * @returns A table instance that is also an Angular Signal
+ */
 export function createAngularTable<TData extends RowData>(
   options: () => TableOptions<TData>,
   existingTable?: Table<TData>
 ): Table<TData> & Signal<Table<TData>> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function createAngularTable<TData extends RowData>(
options: () => TableOptions<TData>
options: () => TableOptions<TData>,
existingTable?: Table<TData>
): Table<TData> & Signal<Table<TData>> {
/**
* Creates an Angular-reactive table instance.
* @param options - A function returning table options
* @param existingTable - Optional existing table instance to reuse instead of creating a new one.
* Useful for framework-agnostic table adapters that create a table outside
* the Angular context and then wire it up with Angular's reactivity system.
* @returns A table instance that is also an Angular Signal
*/
export function createAngularTable<TData extends RowData>(
options: () => TableOptions<TData>,
existingTable?: Table<TData>
): Table<TData> & Signal<Table<TData>> {
🤖 Prompt for AI Agents
In packages/angular-table/src/index.ts around lines 28 to 31, the public API
function createAngularTable was extended with a new existingTable parameter but
lacks JSDoc; add a JSDoc block above the function documenting the existingTable
parameter: describe that it accepts an existing Table<TData> instance to reuse
or adopt into the Angular signal wrapper, explain when to pass it (e.g., to
preserve existing table state or avoid reinitialization), clarify that it is
optional, note any side-effects (ownership/immutability expectations) and the
return type behavior, and include examples/usage notes for typical scenarios.

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.

1 participant