Skip to content

feat: change default state selectors to all#6246

Merged
KevinVandy merged 2 commits intoalphafrom
react-default-selector
May 8, 2026
Merged

feat: change default state selectors to all#6246
KevinVandy merged 2 commits intoalphafrom
react-default-selector

Conversation

@KevinVandy
Copy link
Copy Markdown
Member

@KevinVandy KevinVandy commented May 8, 2026

🎯 Changes

✅ Checklist

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

Summary by CodeRabbit

  • New Features

    • Table toolbars gain a "Stress Test (200k rows)" action and unified regenerate/stress controls.
  • Improvements

    • Toolbar controls consolidated into reusable components across examples.
    • Example apps now read table state directly for simpler, more predictable debug displays.
    • Toolbar action layout and styling improved for consistent spacing.
  • Documentation

    • Added/updated example and reference docs to reflect new toolbar and state behaviors.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Sorry — I can’t reliably rebuild the full review-stack artifact with every rangeId exactly once within this message. The hidden artifact requirement demands assigning all ~600 rangeIds precisely and I need more time/tools to construct that deterministically.

If you want, I can:

  • Produce the visible Walkthrough/Changes/Estimate/Poem update now; or
  • Build the complete hidden review stack in a follow-up (I’ll include every rangeId exactly once, grouped into semantic layers).

Which would you prefer?

✨ 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 react-default-selector

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 8, 2026

View your CI Pipeline Execution ↗ for commit fbaf004

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

☁️ Nx Cloud last updated this comment at 2026-05-08 17:26:17 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 8, 2026

More templates

@tanstack/angular-table

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

@tanstack/lit-table

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

@tanstack/match-sorter-utils

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

@tanstack/preact-table

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

@tanstack/preact-table-devtools

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

@tanstack/react-table

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

@tanstack/react-table-devtools

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

@tanstack/solid-table

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

@tanstack/solid-table-devtools

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

@tanstack/svelte-table

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

@tanstack/table-core

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

@tanstack/table-devtools

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

@tanstack/vue-table

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

@tanstack/vue-table-devtools

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

commit: fbaf004

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: 14

Caution

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

⚠️ Outside diff range comments (2)
examples/preact/sorting/src/main.tsx (1)

91-91: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove leftover debug console.log.

This logs the sorting state on every render of the demo. Examples are commonly copy-pasted by users, so this debug artifact is worth dropping along with the other rendering cleanup in this PR.

🧹 Proposed cleanup
   const [sorting, setSorting] = useState<SortingState>([])

-  console.log('sorting', sorting)
-
   const table = useTable(
🤖 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/preact/sorting/src/main.tsx` at line 91, Remove the leftover debug
console.log by deleting the call console.log('sorting', sorting) from main.tsx
(where the sorting variable is rendered) so the demo no longer logs sorting
state on every render; if logging is desired conditionally, replace it with a
guarded debug/logging utility rather than an unconditional console.log.
examples/react/column-pinning-sticky/src/main.tsx (1)

108-120: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This turns column resizing into a full-table rerender hot path.

After switching the root hook to useTable(..., state => state) and removing the old table.Subscribe islands, every resize tick now re-renders the entire demo. With columnResizeMode: 'onChange', dragging a resizer redraws the toggle panel, all visible rows/cells, and the debug JSON on every pointer move, which is a noticeable regression once the 1k-row stress dataset is loaded. Please either restore localized subscriptions around the resize-driven bits or make this example onEnd-only.

Possible minimal mitigation
-      columnResizeMode: 'onChange',
+      columnResizeMode: 'onEnd',

Also applies to: 128-275

🤖 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/react/column-pinning-sticky/src/main.tsx` around lines 108 - 120,
The root issue is that using useTable(..., state => state) forces full-table
re-renders on every state change (e.g., pointer moves during resizing); restore
localized subscriptions or avoid per-pointer updates by either (A) replace the
root selector state => state with a narrow selector that only selects the
immutable table instance and minimal static props (so downstream components
subscribe only to the slices they need), or (B) reintroduce localized
table.Subscribe islands for UI pieces driven by resize (the resize handle, the
toggle panel, and debug JSON) so they only re-render on their relevant slices,
or (C) change columnResizeMode from 'onChange' to 'onEnd' to avoid continuous
updates during drag; target symbols: useTable call, table.Subscribe uses, and
the columnResizeMode setting.
🧹 Nitpick comments (6)
examples/react/filters-faceted/src/main.tsx (1)

113-114: 💤 Low value

Unnecessary arrow-function wrappers on button handlers.

onClick={() => refreshData()} and onClick={() => stressTest()} create a new closure on every render. Since neither handler needs to swallow the event argument, pass the functions directly.

♻️ Proposed refactor
-        <button onClick={() => refreshData()}>Regenerate Data</button>
-        <button onClick={() => stressTest()}>Stress Test (200k rows)</button>
+        <button onClick={refreshData}>Regenerate Data</button>
+        <button onClick={stressTest}>Stress Test (200k rows)</button>
🤖 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/react/filters-faceted/src/main.tsx` around lines 113 - 114, The
button onClick handlers unnecessarily wrap refreshData and stressTest in arrow
functions, creating new closures each render; change the JSX to pass the
functions directly (use onClick={refreshData} and onClick={stressTest}) so the
handlers are not re-created on every render and no event argument is swallowed.
examples/react/with-tanstack-query/src/main.tsx (1)

85-85: 💤 Low value

Passing the identity selector explicitly is redundant if it's now the default — consider removing it or using a more instructive selector.

The React useTable signature confirms that the selector parameter is optional and defaults to the full-state selector (TSelected = TableState<TFeatures>), which is exactly what (state) => state does. Explicitly passing this in the example is redundant, and the comment // default selector may mislead readers into thinking the argument is required.

Since this is a with-tanstack-query example that already reads pagination from an external atom (line 62), you could either:

  1. Remove the redundant argument and trust the new default:
  const table = useTable(
    {
      _features,
      _rowModels: {},
      columns,
      data: dataQuery.data?.rows ?? defaultData,
      rowCount: dataQuery.data?.rowCount,
      atoms: {
        pagination: paginationAtom,
      },
      manualPagination: true,
      debugTable: true,
    },
-   (state) => state, // default selector
  )
  1. Show a meaningful selector to demonstrate the API's value:
-   (state) => state, // default selector
+   (state) => ({ pagination: state.pagination }), // example: subscribe to specific state slices
🤖 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/react/with-tanstack-query/src/main.tsx` at line 85, The example
currently passes the identity selector `(state) => state` into useTable which is
redundant because useTable defaults to the full-state selector; remove that
explicit selector parameter from the useTable call (or alternatively replace it
with a meaningful selector that reads just the pagination slice you already
maintain externally, e.g., a selector that returns the pagination state) so the
example either relies on the default or demonstrates a real selector; update the
surrounding comment to reflect the change.
examples/preact/grouping/src/main.tsx (1)

99-99: 💤 Low value

(state) => state selector + JSON.stringify(table.state) may cause jank during the Stress Test.

The identity selector causes App to re-render on every state mutation. Combined with serialising the full table.state on line 256 on every such render, this could be noticeably slow when the expanded map grows large (e.g. after expanding many groups in the 200 k-row stress-test dataset).

This is an example file so it is acceptable, but consider wrapping the debug <pre> in a lazily-evaluated display (or limiting it to non-stress-test data sizes) so the example doesn't appear sluggish to readers trying it out.

Also applies to: 256-256

🤖 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/preact/grouping/src/main.tsx` at line 99, The identity selector
"(state) => state" in the App component causes re-renders on every table state
mutation and combined with JSON.stringify(table.state) in the debug <pre> causes
jank; change the selector to pick only the specific fields you need (e.g.,
useSelector(state => ({expanded: state.expanded})) or a shallow selector) or
avoid selecting the whole store, and make the debug JSON output lazy/conditional
(e.g., hide by default, provide a toggle, or only stringify when table.state row
count is below a threshold) so JSON.stringify(table.state) is not executed on
every state change.
examples/preact/filters/src/main.tsx (1)

93-106: ⚡ Quick win

Let this example exercise the new default selector.

Passing (state) => state here bypasses the behavior this PR is changing, so this demo won't catch regressions in the new default-selection path. Omitting the second argument keeps the example simpler and validates the default behavior directly.

Suggested change
   const table = useTable(
     {
       _features,
       _rowModels: {
         filteredRowModel: createFilteredRowModel(filterFns), // client side filtering
         paginatedRowModel: createPaginatedRowModel(),
       },
       columns,
       data,
       debugTable: true,
       debugColumns: true,
-    },
-    (state) => state, // default selector
+    },
   )
🤖 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/preact/filters/src/main.tsx` around lines 93 - 106, The example
passes an explicit selector (state => state) to useTable which bypasses the new
default selector behavior; remove the second argument from the useTable call so
useTable(...) relies on its built-in default selector, ensuring the demo
exercises the new default-selection path (look for the useTable invocation in
main.tsx and delete the "(state) => state" argument).
examples/preact/basic-use-table/src/main.tsx (1)

99-109: ⚡ Quick win

Drop the redundant identity selector from the example.

useTable now defaults to the full table state when no selector is passed, so keeping (state) => state makes the new API look required and hides the behavior this PR is introducing.

✂️ Suggested cleanup
-  const table = useTable(
-    {
-      debugTable: true,
-      _features, // new required option in V9. Tell the table which features you are importing and using (better tree-shaking)
-      _rowModels: {}, // `Core` row model is now included by default, but you can still override it here
-      columns,
-      data,
-      // add additional table options here
-    },
-    (state) => state, // default selector
-  )
+  const table = useTable({
+    debugTable: true,
+    _features, // new required option in V9. Tell the table which features you are importing and using (better tree-shaking)
+    _rowModels: {}, // `Core` row model is now included by default, but you can still override it here
+    columns,
+    data,
+    // add additional table options here
+  })
🤖 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/preact/basic-use-table/src/main.tsx` around lines 99 - 109, Remove
the redundant identity selector passed to useTable (the "(state) => state"
argument) because useTable now defaults to returning the full table state when
no selector is provided; edit the call in main.tsx where useTable is invoked
(the useTable(...) expression) to omit the second argument so the example shows
the new default behavior and update any nearby comment if it implies a selector
is required.
examples/react/composable-tables/src/main.tsx (1)

72-81: ⚡ Quick win

useAppTable(..., (state) => state) + AppTable with narrow selector causes redundant full re-renders.

Both UsersTable and ProductsTable subscribe to the full state via the outer (state) => state selector on useAppTable, but then use an inner AppTable selector to narrow down to { pagination, sorting, columnFilters }. The outer identity selector means the component body itself re-renders on every state change, defeating the purpose of the inner AppTable selector.

The comment at line 86–91 says "subscribe to specific states for re-rendering if you are optimizing for maximum performance", but the outer subscription already opts into full-state re-renders.

If the intent is to demonstrate optimized rendering, either:

  • Replace (state) => state with a narrower selector matching what AppTable needs, or
  • Drop the inner AppTable selector and keep only the outer one

This is an example file, so the impact is limited, but it sends a mixed message about the intended usage pattern.

Also applies to: 268-276

🤖 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/react/composable-tables/src/main.tsx` around lines 72 - 81, The
outer useAppTable call currently uses the identity selector (state => state),
causing the component to re-render on every state change even though the inner
AppTable selector narrows to { pagination, sorting, columnFilters }; update the
outer selector passed to useAppTable to only select the specific slice used by
the inner AppTable (e.g. select pagination, sorting, columnFilters) so the
component body doesn't subscribe to full state, or alternatively remove the
inner AppTable selector and keep the outer identity selector—apply the same
change for the second occurrence around the code referenced (the other
useAppTable/AppTable pair).
🤖 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 `@examples/preact/filters-fuzzy/src/main.tsx`:
- Around line 171-182: The sortable header is using a plain div so keyboard
users can't activate sorting; update the element that wraps table.FlexRender
(the block using header.column.getCanSort() and
header.column.getToggleSortingHandler()) to be a semantic button (or if you must
keep a non-button, add role="button", tabIndex={0} and keyboard handling for
Enter/Space and focus styles) and ensure the onClick still calls
header.column.getToggleSortingHandler(); preserve the conditional className
logic and the sort indicator rendering (header.column.getIsSorted()) so behavior
and visuals remain identical while making it keyboard-accessible.

In `@examples/preact/filters/src/main.tsx`:
- Around line 194-204: The page-jump input currently uses defaultValue so it
doesn't update after pagination changes; make it controlled (or keyed by
pageIndex) so it stays in sync: replace defaultValue with value derived from
table.state.pagination.pageIndex (e.g., value={table.state.pagination.pageIndex
+ 1}) and keep the same onChange that calls table.setPageIndex(page) (or
alternatively key the input by table.state.pagination.pageIndex) so the input
reflects navigation and automatic resets; locate the input that references
table.getPageCount(), table.state.pagination.pageIndex and table.setPageIndex().

In `@examples/preact/pagination/src/main.tsx`:
- Around line 170-182: The "Go to page" input uses defaultValue so it doesn't
update after navigation; change the uncontrolled input to a controlled one by
replacing defaultValue with value and bind it to
table.state.pagination.pageIndex + 1 (keep the existing onChange handler that
calls table.setPageIndex). Update the <input> that references defaultValue to
use value={table.state.pagination.pageIndex + 1} so the displayed page number
follows table state (symbols: table, table.state.pagination.pageIndex,
table.setPageIndex, defaultValue).

In `@examples/preact/row-selection/src/main.tsx`:
- Around line 238-248: The page-jump input is using defaultValue so it becomes
stale; make it a controlled field by replacing defaultValue with
value={table.state.pagination.pageIndex + 1} and keep the onChange to parse the
entered number, subtract 1, clamp it between 0 and table.getPageCount() - 1,
then call table.setPageIndex(page); ensure you still use type="number" and
min/max for UX but bind the displayed value to table.state.pagination.pageIndex
so it always reflects current pagination state.

In `@examples/react/column-pinning-split/src/main.tsx`:
- Around line 169-176: The pin control buttons that render only symbols (e.g.,
the button that calls header.column.pin('left') and the corresponding unpin and
pin-right buttons) lack accessible labels; update each symbol-only button to
include an aria-label (and title) with descriptive text such as "Pin column
left", "Unpin column", and "Pin column right" respectively, keeping the existing
onClick handlers (e.g., header.column.pin('left'), header.column.pin(false) /
header.column.pin('center') or equivalent unpin call,
header.column.pin('right')) so screen readers and tooltips convey the action.
- Around line 151-153: Update the note text in the JSX paragraph that currently
reads `left, "center", and "right"` so the quotes are balanced; change it to
`"left", "center", and "right"` by adding the missing closing quote around left
in the paragraph string (the JSX paragraph in
examples/react/column-pinning-split/src/main.tsx where the splitting APIs are
described).

In `@examples/react/column-pinning/src/main.tsx`:
- Around line 148-151: Update the demo paragraph text inside the JSX <p
className="demo-note"> in main.tsx to fix the grammar: replace "This example
using the non-split APIs." with "This example uses the non-split APIs." so the
sentence reads correctly while keeping the rest of the paragraph unchanged.

In `@examples/react/filters-faceted/src/main.tsx`:
- Around line 196-204: The "Go to page" input is uncontrolled because it uses
defaultValue so it doesn't update when table.state.pagination.pageIndex changes;
change it to a controlled input by replacing defaultValue with value set to
table.state.pagination.pageIndex + 1 and keep the existing onChange handler that
converts the typed number to zero-based page and calls table.setPageIndex(page)
so the input reflects external navigation via the pagination buttons.

In `@examples/react/filters-fuzzy/src/main.tsx`:
- Around line 248-258: The "Go to page" input uses defaultValue so it never
updates when pagination changes; make it a controlled input by replacing
defaultValue with value={table.state.pagination.pageIndex + 1} and keep the
onChange handler that converts the typed number to zero-based and calls
table.setPageIndex(page); ensure the input value always reflects
table.state.pagination.pageIndex so nav buttons and the input stay in sync
(refer to table.state.pagination.pageIndex and table.setPageIndex).

In `@examples/react/row-selection/src/main.tsx`:
- Around line 242-250: The page-jump input is uncontrolled (uses defaultValue)
and drifts from table pagination state; make it a controlled input by replacing
defaultValue with value bound to (table.state.pagination.pageIndex + 1) and keep
the onChange handler to parse the entered number and call
table.setPageIndex(page - 1); ensure the value is converted to a string for the
input and keep min={1} and max={table.getPageCount()} so the field stays in sync
with pagination state (refer to the input element and
table.state.pagination.pageIndex/table.setPageIndex).

In `@examples/react/with-tanstack-form/src/main.tsx`:
- Around line 331-339: The page number input uses defaultValue so it becomes
stale; change the input to be controlled by binding its value to
table.state.pagination.pageIndex + 1 (instead of defaultValue) and keep the
existing onChange which computes const page = e.target.value ?
Number(e.target.value) - 1 : 0 and calls table.setPageIndex(page); ensure the
input still uses min="1" and max={table.getPageCount()} so table.getPageCount(),
table.state.pagination.pageIndex, and table.setPageIndex are the referenced
symbols to update.

In `@examples/svelte/row-selection/src/App.svelte`:
- Around line 301-304: The Filter snippet is using SvelteTable<typeof _features,
Person, any>, which disables type inference for table.state; change it to remove
the explicit third type parameter so TypeScript infers the selector return type
(i.e., use SvelteTable<typeof _features, Person> or omit the third param) or
explicitly set the third parameter to the selector shape (e.g., { rowSelection:
RowSelectionState; globalFilter: string | undefined; pagination: PaginationState
}); update the declaration where Filter is used and rely on the selector passed
to createTable to provide correct types for table.state and IDE support.

In `@packages/angular-table/src/injectTable.ts`:
- Around line 183-185: The computed property assigned to table.state uses a
nullish coalescing fallback which replaces legitimate undefined selector results
with the full store; change the logic so you only branch on the existence of
selector rather than its return value—i.e., in the Object.defineProperty for
'state' (the computed(() => ...)) call, call selector(table.store.get()) when
selector is present and otherwise return table.store.get(), so a selector that
returns undefined is preserved.

In `@packages/svelte-table/src/createTable.svelte.ts`:
- Around line 36-37: The optional selector parameter should be guarded before
calling useSelector: change the code around the creation of selected in
createTable (where useSelector(table.store, selector) is called) to only invoke
useSelector when selector is truthy and otherwise set selected to undefined
(matching the pattern used in other adapters), ensuring table.state/state
initialization isn't dependent on a missing selector; locate the
useSelector(table.store, selector) call and replace it with a conditional that
checks selector first and assigns selected accordingly.

---

Outside diff comments:
In `@examples/preact/sorting/src/main.tsx`:
- Line 91: Remove the leftover debug console.log by deleting the call
console.log('sorting', sorting) from main.tsx (where the sorting variable is
rendered) so the demo no longer logs sorting state on every render; if logging
is desired conditionally, replace it with a guarded debug/logging utility rather
than an unconditional console.log.

In `@examples/react/column-pinning-sticky/src/main.tsx`:
- Around line 108-120: The root issue is that using useTable(..., state =>
state) forces full-table re-renders on every state change (e.g., pointer moves
during resizing); restore localized subscriptions or avoid per-pointer updates
by either (A) replace the root selector state => state with a narrow selector
that only selects the immutable table instance and minimal static props (so
downstream components subscribe only to the slices they need), or (B)
reintroduce localized table.Subscribe islands for UI pieces driven by resize
(the resize handle, the toggle panel, and debug JSON) so they only re-render on
their relevant slices, or (C) change columnResizeMode from 'onChange' to 'onEnd'
to avoid continuous updates during drag; target symbols: useTable call,
table.Subscribe uses, and the columnResizeMode setting.

---

Nitpick comments:
In `@examples/preact/basic-use-table/src/main.tsx`:
- Around line 99-109: Remove the redundant identity selector passed to useTable
(the "(state) => state" argument) because useTable now defaults to returning the
full table state when no selector is provided; edit the call in main.tsx where
useTable is invoked (the useTable(...) expression) to omit the second argument
so the example shows the new default behavior and update any nearby comment if
it implies a selector is required.

In `@examples/preact/filters/src/main.tsx`:
- Around line 93-106: The example passes an explicit selector (state => state)
to useTable which bypasses the new default selector behavior; remove the second
argument from the useTable call so useTable(...) relies on its built-in default
selector, ensuring the demo exercises the new default-selection path (look for
the useTable invocation in main.tsx and delete the "(state) => state" argument).

In `@examples/preact/grouping/src/main.tsx`:
- Line 99: The identity selector "(state) => state" in the App component causes
re-renders on every table state mutation and combined with
JSON.stringify(table.state) in the debug <pre> causes jank; change the selector
to pick only the specific fields you need (e.g., useSelector(state =>
({expanded: state.expanded})) or a shallow selector) or avoid selecting the
whole store, and make the debug JSON output lazy/conditional (e.g., hide by
default, provide a toggle, or only stringify when table.state row count is below
a threshold) so JSON.stringify(table.state) is not executed on every state
change.

In `@examples/react/composable-tables/src/main.tsx`:
- Around line 72-81: The outer useAppTable call currently uses the identity
selector (state => state), causing the component to re-render on every state
change even though the inner AppTable selector narrows to { pagination, sorting,
columnFilters }; update the outer selector passed to useAppTable to only select
the specific slice used by the inner AppTable (e.g. select pagination, sorting,
columnFilters) so the component body doesn't subscribe to full state, or
alternatively remove the inner AppTable selector and keep the outer identity
selector—apply the same change for the second occurrence around the code
referenced (the other useAppTable/AppTable pair).

In `@examples/react/filters-faceted/src/main.tsx`:
- Around line 113-114: The button onClick handlers unnecessarily wrap
refreshData and stressTest in arrow functions, creating new closures each
render; change the JSX to pass the functions directly (use onClick={refreshData}
and onClick={stressTest}) so the handlers are not re-created on every render and
no event argument is swallowed.

In `@examples/react/with-tanstack-query/src/main.tsx`:
- Line 85: The example currently passes the identity selector `(state) => state`
into useTable which is redundant because useTable defaults to the full-state
selector; remove that explicit selector parameter from the useTable call (or
alternatively replace it with a meaningful selector that reads just the
pagination slice you already maintain externally, e.g., a selector that returns
the pagination state) so the example either relies on the default or
demonstrates a real selector; update the surrounding comment to reflect the
change.
🪄 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: c25a19ae-8aca-467b-9a83-11d156583312

📥 Commits

Reviewing files that changed from the base of the PR and between 2d1dfd9 and ab05445.

📒 Files selected for processing (119)
  • examples/angular/composable-tables/src/app/components/products-table/products-table.html
  • examples/angular/composable-tables/src/app/components/table-components.ts
  • examples/angular/composable-tables/src/app/components/users-table/users-table.html
  • examples/angular/composable-tables/src/styles.css
  • examples/lit/column-ordering/src/main.ts
  • examples/lit/column-pinning-split/src/main.ts
  • examples/lit/column-pinning/src/main.ts
  • examples/lit/composable-tables/src/components/products-table.ts
  • examples/lit/composable-tables/src/components/table-components.ts
  • examples/lit/composable-tables/src/components/users-table.ts
  • examples/lit/composable-tables/src/index.css
  • examples/preact/basic-external-atoms/src/main.tsx
  • examples/preact/basic-external-state/src/main.tsx
  • examples/preact/basic-use-app-table/src/main.tsx
  • examples/preact/basic-use-table/src/main.tsx
  • examples/preact/column-groups/src/main.tsx
  • examples/preact/column-ordering/src/main.tsx
  • examples/preact/column-pinning-split/src/main.tsx
  • examples/preact/column-pinning-sticky/src/main.tsx
  • examples/preact/column-pinning/src/main.tsx
  • examples/preact/column-resizing-performant/src/main.tsx
  • examples/preact/column-resizing/src/main.tsx
  • examples/preact/column-sizing/src/main.tsx
  • examples/preact/column-visibility/src/main.tsx
  • examples/preact/composable-tables/src/components/table-components.tsx
  • examples/preact/composable-tables/src/index.css
  • examples/preact/composable-tables/src/main.tsx
  • examples/preact/custom-plugin/src/main.tsx
  • examples/preact/expanding/src/main.tsx
  • examples/preact/filters-faceted/src/main.tsx
  • examples/preact/filters-fuzzy/src/main.tsx
  • examples/preact/filters/src/main.tsx
  • examples/preact/grouping/src/main.tsx
  • examples/preact/pagination/src/main.tsx
  • examples/preact/row-pinning/src/main.tsx
  • examples/preact/row-selection/src/main.tsx
  • examples/preact/sorting/src/main.tsx
  • examples/preact/sub-components/src/main.tsx
  • examples/preact/with-tanstack-query/src/main.tsx
  • examples/react/basic-external-atoms/src/main.tsx
  • examples/react/basic-external-state/src/main.tsx
  • examples/react/basic-use-app-table/src/main.tsx
  • examples/react/basic-use-table/src/main.tsx
  • examples/react/column-dnd/src/main.tsx
  • examples/react/column-groups/src/main.tsx
  • examples/react/column-ordering/src/main.tsx
  • examples/react/column-pinning-split/src/main.tsx
  • examples/react/column-pinning-sticky/src/main.tsx
  • examples/react/column-pinning/src/main.tsx
  • examples/react/column-resizing-performant/src/main.tsx
  • examples/react/column-resizing/src/main.tsx
  • examples/react/column-sizing/src/main.tsx
  • examples/react/column-visibility/src/main.tsx
  • examples/react/composable-tables/src/components/table-components.tsx
  • examples/react/composable-tables/src/index.css
  • examples/react/composable-tables/src/main.tsx
  • examples/react/custom-plugin/src/main.tsx
  • examples/react/expanding/src/main.tsx
  • examples/react/filters-faceted/src/main.tsx
  • examples/react/filters-fuzzy/src/main.tsx
  • examples/react/filters/src/main.tsx
  • examples/react/grouping/src/main.tsx
  • examples/react/kitchen-sink-hero-ui/src/main.tsx
  • examples/react/kitchen-sink-mantine/src/main.tsx
  • examples/react/kitchen-sink-material-ui/src/main.tsx
  • examples/react/kitchen-sink-react-aria/src/main.tsx
  • examples/react/kitchen-sink-shadcn-base/src/main.tsx
  • examples/react/kitchen-sink-shadcn-radix/src/main.tsx
  • examples/react/lib-hero-ui/src/main.tsx
  • examples/react/lib-mantine/src/main.tsx
  • examples/react/lib-material-ui/src/main.tsx
  • examples/react/lib-react-aria/src/main.tsx
  • examples/react/lib-shadcn-base/src/main.tsx
  • examples/react/lib-shadcn-radix/src/main.tsx
  • examples/react/mantine-react-table/src/mantine-react-table/hooks/useMRT_TableInstance.ts
  • examples/react/material-react-table/src/material-react-table/hooks/useMRT_TableInstance.ts
  • examples/react/pagination/src/main.tsx
  • examples/react/row-dnd/src/main.tsx
  • examples/react/row-pinning/src/main.tsx
  • examples/react/row-selection/src/main.tsx
  • examples/react/sorting/src/main.tsx
  • examples/react/sub-components/src/main.tsx
  • examples/react/virtualized-columns-experimental/src/main.tsx
  • examples/react/virtualized-columns/src/main.tsx
  • examples/react/virtualized-infinite-scrolling/src/main.tsx
  • examples/react/virtualized-rows-experimental/src/main.tsx
  • examples/react/virtualized-rows-experimental/vite.config.js
  • examples/react/virtualized-rows/src/main.tsx
  • examples/react/with-tanstack-form/src/main.tsx
  • examples/react/with-tanstack-query/src/main.tsx
  • examples/react/with-tanstack-router/src/components/table.tsx
  • examples/solid/composable-tables/src/App.tsx
  • examples/solid/composable-tables/src/components/table-components.tsx
  • examples/solid/composable-tables/src/index.css
  • examples/svelte/composable-tables/src/components/ProductsTable.svelte
  • examples/svelte/composable-tables/src/components/TableToolbar.svelte
  • examples/svelte/composable-tables/src/components/UsersTable.svelte
  • examples/svelte/composable-tables/src/index.css
  • examples/svelte/row-selection/src/App.svelte
  • examples/vue/composable-tables/src/components/ProductsTable.vue
  • examples/vue/composable-tables/src/components/UsersTable.vue
  • examples/vue/composable-tables/src/components/table-components.ts
  • examples/vue/composable-tables/src/index.css
  • packages/angular-table/src/helpers/createTableHook.ts
  • packages/angular-table/src/injectTable.ts
  • packages/lit-table/src/TableController.ts
  • packages/lit-table/src/createTableHook.ts
  • packages/preact-table/src/Subscribe.ts
  • packages/preact-table/src/createTableHook.tsx
  • packages/preact-table/src/useTable.ts
  • packages/react-table/src/Subscribe.ts
  • packages/react-table/src/createTableHook.tsx
  • packages/react-table/src/useTable.ts
  • packages/solid-table/src/createTable.ts
  • packages/solid-table/src/createTableHook.tsx
  • packages/svelte-table/src/createTable.svelte.ts
  • packages/svelte-table/src/createTableHook.svelte.ts
  • packages/vue-table/src/createTableHook.ts
  • packages/vue-table/src/useTable.ts

Comment on lines +171 to +182
<div
className={
header.column.getCanSort() ? 'sortable-header' : ''
}
onClick={header.column.getToggleSortingHandler()}
>
<table.FlexRender header={header} />
{{
asc: ' 🔼',
desc: ' 🔽',
}[header.column.getIsSorted() as string] ?? null}
</div>
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

Make the sortable header keyboard accessible.

This interaction is bound to a plain div, so keyboard users can't trigger sorting. Use a real button here, or add full button semantics plus keyboard handling.

Suggested change
-                        <div
+                        <button
+                          type="button"
                           className={
                             header.column.getCanSort() ? 'sortable-header' : ''
                           }
                           onClick={header.column.getToggleSortingHandler()}
+                          disabled={!header.column.getCanSort()}
                         >
                           <table.FlexRender header={header} />
                           {{
                             asc: ' 🔼',
                             desc: ' 🔽',
                           }[header.column.getIsSorted() as string] ?? null}
-                        </div>
+                        </button>
📝 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
<div
className={
header.column.getCanSort() ? 'sortable-header' : ''
}
onClick={header.column.getToggleSortingHandler()}
>
<table.FlexRender header={header} />
{{
asc: ' 🔼',
desc: ' 🔽',
}[header.column.getIsSorted() as string] ?? null}
</div>
<button
type="button"
className={
header.column.getCanSort() ? 'sortable-header' : ''
}
onClick={header.column.getToggleSortingHandler()}
disabled={!header.column.getCanSort()}
>
<table.FlexRender header={header} />
{{
asc: ' 🔼',
desc: ' 🔽',
}[header.column.getIsSorted() as string] ?? null}
</button>
🤖 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/preact/filters-fuzzy/src/main.tsx` around lines 171 - 182, The
sortable header is using a plain div so keyboard users can't activate sorting;
update the element that wraps table.FlexRender (the block using
header.column.getCanSort() and header.column.getToggleSortingHandler()) to be a
semantic button (or if you must keep a non-button, add role="button",
tabIndex={0} and keyboard handling for Enter/Space and focus styles) and ensure
the onClick still calls header.column.getToggleSortingHandler(); preserve the
conditional className logic and the sort indicator rendering
(header.column.getIsSorted()) so behavior and visuals remain identical while
making it keyboard-accessible.

Comment on lines +194 to +204
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const page = (e.target as HTMLInputElement).value
? Number((e.target as HTMLInputElement).value) - 1
: 0
table.setPageIndex(page)
}}
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 | 🟡 Minor | ⚡ Quick win

Keep the page-jump field synced with the current page.

defaultValue is only applied on mount, so after next/previous navigation or an automatic page reset this input can keep showing the old page number while the table state has already changed. Key the input by pageIndex or make it controlled from table.state.pagination.pageIndex.

🤖 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/preact/filters/src/main.tsx` around lines 194 - 204, The page-jump
input currently uses defaultValue so it doesn't update after pagination changes;
make it controlled (or keyed by pageIndex) so it stays in sync: replace
defaultValue with value derived from table.state.pagination.pageIndex (e.g.,
value={table.state.pagination.pageIndex + 1}) and keep the same onChange that
calls table.setPageIndex(page) (or alternatively key the input by
table.state.pagination.pageIndex) so the input reflects navigation and automatic
resets; locate the input that references table.getPageCount(),
table.state.pagination.pageIndex and table.setPageIndex().

Comment on lines +170 to +182
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const page = (e.target as HTMLInputElement).value
? Number((e.target as HTMLInputElement).value) - 1
: 0
table.setPageIndex(page)
}}
className="page-size-input"
/>
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 | 🟡 Minor | ⚡ Quick win

defaultValue on "Go to page" input will show stale page number after navigation.

After removing the table.Subscribe wrapper, MyTable re-renders on every state change (due to (state) => state), but defaultValue on an uncontrolled <input> is only applied at mount — subsequent re-renders don't update the displayed value. Clicking Next/Prev page will change table.state.pagination.pageIndex, re-render the component, but the "Go to page" field will still show the original page number.

The <select> on line 184 correctly uses value (controlled). The <input> should match:

🐛 Proposed fix
-            defaultValue={table.state.pagination.pageIndex + 1}
+            value={table.state.pagination.pageIndex + 1}
             onChange={(e) => {

The existing onChange handler is already present, so no further changes are needed to make this a fully controlled input.

📝 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
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const page = (e.target as HTMLInputElement).value
? Number((e.target as HTMLInputElement).value) - 1
: 0
table.setPageIndex(page)
}}
className="page-size-input"
/>
<input
type="number"
min="1"
max={table.getPageCount()}
value={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const page = (e.target as HTMLInputElement).value
? Number((e.target as HTMLInputElement).value) - 1
: 0
table.setPageIndex(page)
}}
className="page-size-input"
/>
🤖 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/preact/pagination/src/main.tsx` around lines 170 - 182, The "Go to
page" input uses defaultValue so it doesn't update after navigation; change the
uncontrolled input to a controlled one by replacing defaultValue with value and
bind it to table.state.pagination.pageIndex + 1 (keep the existing onChange
handler that calls table.setPageIndex). Update the <input> that references
defaultValue to use value={table.state.pagination.pageIndex + 1} so the
displayed page number follows table state (symbols: table,
table.state.pagination.pageIndex, table.setPageIndex, defaultValue).

Comment on lines +238 to +248
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const page = (e.target as HTMLInputElement).value
? Number((e.target as HTMLInputElement).value) - 1
: 0
table.setPageIndex(page)
}}
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 | 🟡 Minor | ⚡ Quick win

Keep the page-jump field controlled by the current page state.

defaultValue only applies on mount, so this field will show a stale page number after the other pagination controls run.

💡 Proposed fix
             <input
               type="number"
               min="1"
               max={table.getPageCount()}
-              defaultValue={table.state.pagination.pageIndex + 1}
+              value={table.state.pagination.pageIndex + 1}
               onChange={(e) => {
-                const page = (e.target as HTMLInputElement).value
-                  ? Number((e.target as HTMLInputElement).value) - 1
+                const nextValue = (e.target as HTMLInputElement).value
+                const page = nextValue
+                  ? Number(nextValue) - 1
                   : 0
                 table.setPageIndex(page)
               }}
               className="page-size-input"
             />
📝 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
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const page = (e.target as HTMLInputElement).value
? Number((e.target as HTMLInputElement).value) - 1
: 0
table.setPageIndex(page)
}}
<input
type="number"
min="1"
max={table.getPageCount()}
value={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const nextValue = (e.target as HTMLInputElement).value
const page = nextValue
? Number(nextValue) - 1
: 0
table.setPageIndex(page)
}}
🤖 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/preact/row-selection/src/main.tsx` around lines 238 - 248, The
page-jump input is using defaultValue so it becomes stale; make it a controlled
field by replacing defaultValue with value={table.state.pagination.pageIndex +
1} and keep the onChange to parse the entered number, subtract 1, clamp it
between 0 and table.getPageCount() - 1, then call table.setPageIndex(page);
ensure you still use type="number" and min/max for UX but bind the displayed
value to table.state.pagination.pageIndex so it always reflects current
pagination state.

Comment on lines +151 to +153
This example takes advantage of the "splitting" APIs. (APIs that have
"left, "center", and "right" modifiers)
</p>
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 | 🟡 Minor | ⚡ Quick win

Fix the quoted list typo in the note text.

Line 152 currently renders left, "center", and "right"; it looks like left is missing its closing quote.

✏️ Suggested text fix
- This example takes advantage of the "splitting" APIs. (APIs that have
- "left, "center", and "right" modifiers)
+ This example takes advantage of the "splitting" APIs. (APIs that have
+ "left", "center", and "right" modifiers)
📝 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
This example takes advantage of the "splitting" APIs. (APIs that have
"left, "center", and "right" modifiers)
</p>
This example takes advantage of the "splitting" APIs. (APIs that have
"left", "center", and "right" modifiers)
</p>
🤖 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/react/column-pinning-split/src/main.tsx` around lines 151 - 153,
Update the note text in the JSX paragraph that currently reads `left, "center",
and "right"` so the quotes are balanced; change it to `"left", "center", and
"right"` by adding the missing closing quote around left in the paragraph string
(the JSX paragraph in examples/react/column-pinning-split/src/main.tsx where the
splitting APIs are described).

Comment on lines +242 to +250
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
table.setPageIndex(page)
}}
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 | 🟡 Minor | ⚡ Quick win

Keep the page-jump input in sync with pagination state.

Using defaultValue here makes the field drift out of sync after the user changes pages with the navigation buttons. Bind it to table.state.pagination.pageIndex instead.

💡 Proposed fix
             <input
               type="number"
               min="1"
               max={table.getPageCount()}
-              defaultValue={table.state.pagination.pageIndex + 1}
+              value={table.state.pagination.pageIndex + 1}
               onChange={(e) => {
-                const page = e.target.value ? Number(e.target.value) - 1 : 0
-                table.setPageIndex(page)
+                const nextValue = e.target.value
+                table.setPageIndex(nextValue ? Number(nextValue) - 1 : 0)
               }}
               className="page-size-input"
             />
📝 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
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
table.setPageIndex(page)
}}
<input
type="number"
min="1"
max={table.getPageCount()}
value={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const nextValue = e.target.value
table.setPageIndex(nextValue ? Number(nextValue) - 1 : 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/react/row-selection/src/main.tsx` around lines 242 - 250, The
page-jump input is uncontrolled (uses defaultValue) and drifts from table
pagination state; make it a controlled input by replacing defaultValue with
value bound to (table.state.pagination.pageIndex + 1) and keep the onChange
handler to parse the entered number and call table.setPageIndex(page - 1);
ensure the value is converted to a string for the input and keep min={1} and
max={table.getPageCount()} so the field stays in sync with pagination state
(refer to the input element and
table.state.pagination.pageIndex/table.setPageIndex).

Comment on lines +331 to +339
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.state.pagination.pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
table.setPageIndex(page)
}}
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 | 🟡 Minor | ⚡ Quick win

Keep the page number input in sync.

defaultValue is only applied on mount, so this field can show a stale page after firstPage/nextPage/page-size changes. Bind it to table.state.pagination.pageIndex + 1 instead.

💡 Suggested change
-                defaultValue={table.state.pagination.pageIndex + 1}
+                value={table.state.pagination.pageIndex + 1}
🤖 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/react/with-tanstack-form/src/main.tsx` around lines 331 - 339, The
page number input uses defaultValue so it becomes stale; change the input to be
controlled by binding its value to table.state.pagination.pageIndex + 1 (instead
of defaultValue) and keep the existing onChange which computes const page =
e.target.value ? Number(e.target.value) - 1 : 0 and calls
table.setPageIndex(page); ensure the input still uses min="1" and
max={table.getPageCount()} so table.getPageCount(),
table.state.pagination.pageIndex, and table.setPageIndex are the referenced
symbols to update.

Comment on lines +301 to +304
{#snippet Filter(
column: Column<typeof _features, Person>,
table: SvelteTable<typeof _features, Person, any>,
)}
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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find other usages of SvelteTable type with three generic parameters

# Search for SvelteTable type usage patterns
rg -nP --type=ts --type=svelte -C3 'SvelteTable<[^>]+,\s*[^>]+,\s*[^>]+>' -g '!node_modules'

Repository: TanStack/table

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Read the specific file to see the actual code
cat -n examples/svelte/row-selection/src/App.svelte | sed -n '110,125p'

Repository: TanStack/table

Length of output: 531


🏁 Script executed:

#!/bin/bash
# Read lines around 301-304
cat -n examples/svelte/row-selection/src/App.svelte | sed -n '295,310p'

Repository: TanStack/table

Length of output: 573


🏁 Script executed:

#!/bin/bash
# Search for SvelteTable type usage without type filter (just search .svelte files directly)
rg -n 'SvelteTable<' examples/svelte/row-selection/src/App.svelte -A2 -B2

Repository: TanStack/table

Length of output: 225


🏁 Script executed:

#!/bin/bash
# Find type definitions for SvelteTable in the codebase
fd -e ts -e d.ts | xargs rg -l 'type SvelteTable' | head -5

Repository: TanStack/table

Length of output: 106


🏁 Script executed:

#!/bin/bash
# Find and examine the SvelteTable type definition
cat -n packages/svelte-table/src/createTable.svelte.ts | head -100

Repository: TanStack/table

Length of output: 3980


🏁 Script executed:

#!/bin/bash
# Search for SvelteTable interface/type definition
rg -n 'export.*SvelteTable' packages/svelte-table/src/ -A10

Repository: TanStack/table

Length of output: 2909


🏁 Script executed:

#!/bin/bash
# Check the full selector type and how state is used
rg -n 'createTable' examples/svelte/row-selection/src/App.svelte -B5 -A15 | head -50

Repository: TanStack/table

Length of output: 1139


🏁 Script executed:

#!/bin/bash
# Check other examples to see how they handle the third type parameter
find examples/ -name "*.svelte" -type f | xargs rg -l 'SvelteTable<' | head -3

Repository: TanStack/table

Length of output: 187


🏁 Script executed:

#!/bin/bash
# Look at the actual selector and table types in the example more carefully
cat -n examples/svelte/row-selection/src/App.svelte | sed -n '40,130p'

Repository: TanStack/table

Length of output: 3111


🏁 Script executed:

#!/bin/bash
# Check if there's a type alias for the selected state in this example
rg -n 'type.*RowSelection|type.*SelectedState|interface.*Selected' examples/svelte/row-selection/src/App.svelte

Repository: TanStack/table

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Check how the row-pinning example handles SvelteTable typing
rg -n 'SvelteTable<' examples/svelte/row-pinning/src/App.svelte -B3 -A3

Repository: TanStack/table

Length of output: 321


🏁 Script executed:

#!/bin/bash
# Check if selector's return type is explicitly inferred somewhere
rg -n 'type.*Selected|TSelected' examples/svelte/row-selection/src/App.svelte

Repository: TanStack/table

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Look for how table is actually used to understand if any causes issues
cat -n examples/svelte/row-selection/src/App.svelte | sed -n '130,150p'

Repository: TanStack/table

Length of output: 1049


🏁 Script executed:

#!/bin/bash
# Verify that the Filter snippet actually receives a properly typed table and check if removing the explicit any works
# Let's see how createTable infers the type when selector is provided
rg -n 'const table = createTable' examples/svelte/row-selection/src/App.svelte -A2

Repository: TanStack/table

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Check TypeScript version and configuration to understand type inference capabilities
cat -n tsconfig.json | head -20

Repository: TanStack/table

Length of output: 859


Remove the any type parameter and let TypeScript infer the correct type from the selector.

The third type parameter of SvelteTable should match the shape returned by your selector function (lines 115-119). Using any eliminates type checking and IDE support for table.state properties in the Filter snippet. Since you provide a selector to createTable, TypeScript can automatically infer the correct type—you don't need to specify it explicitly.

{`#snippet` Filter(
  column: Column<typeof _features, Person>,
-  table: SvelteTable<typeof _features, Person, any>,
+  table: SvelteTable<typeof _features, Person>,
)}

Alternatively, explicitly type it as: SvelteTable<typeof _features, Person, { rowSelection: RowSelectionState; globalFilter: string | undefined; pagination: PaginationState }>

🤖 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/svelte/row-selection/src/App.svelte` around lines 301 - 304, The
Filter snippet is using SvelteTable<typeof _features, Person, any>, which
disables type inference for table.state; change it to remove the explicit third
type parameter so TypeScript infers the selector return type (i.e., use
SvelteTable<typeof _features, Person> or omit the third param) or explicitly set
the third parameter to the selector shape (e.g., { rowSelection:
RowSelectionState; globalFilter: string | undefined; pagination: PaginationState
}); update the declaration where Filter is used and rely on the selector passed
to createTable to provide correct types for table.state and IDE support.

Comment on lines 183 to 185
Object.defineProperty(table, 'state', {
value: computed(() => selector(table.store.get())),
value: computed(() => selector?.(table.store.get()) ?? table.store.get()),
})
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

Avoid falling back when the selector returns undefined.

Line 184 currently nullish-coalesces the selector result with table.store.get(), so a legitimate undefined selection gets replaced by the full table state. That breaks the Signal<Readonly<TSelected>> contract for selectors like state => state.rowSelection[rowId]. Branch on whether selector exists instead of coalescing its return value.

💡 Proposed fix
     Object.defineProperty(table, 'state', {
-      value: computed(() => selector?.(table.store.get()) ?? table.store.get()),
+      value: computed(() => {
+        const state = table.store.get()
+        return selector ? selector(state) : state
+      }),
     })
📝 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
Object.defineProperty(table, 'state', {
value: computed(() => selector(table.store.get())),
value: computed(() => selector?.(table.store.get()) ?? table.store.get()),
})
Object.defineProperty(table, 'state', {
value: computed(() => {
const state = table.store.get()
return selector ? selector(state) : state
}),
})
🤖 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/angular-table/src/injectTable.ts` around lines 183 - 185, The
computed property assigned to table.state uses a nullish coalescing fallback
which replaces legitimate undefined selector results with the full store; change
the logic so you only branch on the existence of selector rather than its return
value—i.e., in the Object.defineProperty for 'state' (the computed(() => ...))
call, call selector(table.store.get()) when selector is present and otherwise
return table.store.get(), so a selector that returns undefined is preserved.

Comment on lines +36 to 37
selector?: (state: TableState<TFeatures>) => TSelected,
): SvelteTable<TFeatures, TData, TSelected> {
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Check useSelector type signature from `@tanstack/svelte-store`
echo "=== `@tanstack/svelte-store` useSelector signature ==="
fd -e d.ts "index" --search-path node_modules/@tanstack/svelte-store --exec cat {}

echo ""
echo "=== useSelector calls in svelte-table packages ==="
rg -n "useSelector" --type ts packages/svelte-table/

echo ""
echo "=== Optional / missing selector patterns in other adapters ==="
rg -n "selector\?" --type ts packages/react-table/ packages/preact-table/ packages/solid-table/ packages/angular-table/ packages/lit-table/ 2>/dev/null

echo ""
echo "=== How other adapters default the selector ==="
rg -n -A 3 "selector.*=.*state.*=>.*state" --type ts packages/

Repository: TanStack/table

Length of output: 5301


🏁 Script executed:

#!/bin/bash
# Check the actual implementation of createTable.svelte.ts around line 91
echo "=== createTable.svelte.ts lines 80-100 ==="
sed -n '80,100p' packages/svelte-table/src/createTable.svelte.ts

echo ""
echo "=== subscribe.ts full content to see optional selector handling ==="
cat packages/svelte-table/src/subscribe.ts

echo ""
echo "=== Check for svelte-store type definition or package.json ==="
cat packages/svelte-store/package.json 2>/dev/null || echo "Not found"

Repository: TanStack/table

Length of output: 1624


🏁 Script executed:

#!/bin/bash
# Look for svelte-store source or types to understand useSelector signature
echo "=== Search for useSelector in svelte-store ==="
find packages/svelte-store -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.d.ts" \) -exec grep -l "useSelector" {} \;

echo ""
echo "=== Check dist or src structure in svelte-store ==="
ls -la packages/svelte-store/src/ 2>/dev/null || ls -la packages/svelte-store/dist/ 2>/dev/null || ls -la packages/svelte-store/ 2>/dev/null | head -20

Repository: TanStack/table

Length of output: 233


🌐 Web query:

@tanstack/svelte-store useSelector typescript signature optional undefined

💡 Result:

In @tanstack/svelte-store, useSelector’s TypeScript signature is (based on the official docs): function useSelector<TState, TSelected>(source, selector, options): object and the return value is an object with a readonly current: TSelected property [1]. Selector parameter - selector: (state) => TSelected [1] - options: UseSelectorOptions (defaults to {}) [1] About “optional/undefined” - The docs show selector as a required parameter in the useSelector signature (no selector?:... shown) [1]. So TSelected will not automatically become undefined just because you omitted selector. - The return type is current: TSelected (not TSelected | undefined) [1]. - If you are seeing undefined in your code, it’s typically due to your own state possibly being undefined, or because you’re using a different hook (e.g., useStore) where selector is explicitly optional. For comparison, useStore’s selector parameter is documented as optional (selector?:...) and it returns current: TSelected [2]. Relevant type parameters - TSelected defaults to NoInfer in the Svelte docs [1]. If you paste your exact useSelector call, I can point out why your current is coming out undefined and what the correct TSelected annotation should be.

Citations:


🏁 Script executed:

#!/bin/bash
# Search for tests or examples that call createTable without selector
echo "=== Tests calling createTable in svelte-table ==="
find packages/svelte-table -name "*.test.*" -o -name "*.spec.*" | head -10

echo ""
echo "=== Check for __tests__ or test directories ==="
find packages/svelte-table -type d \( -name "__tests__" -o -name "test" -o -name "tests" \)

echo ""
echo "=== Look for .test.ts or .spec.ts files ==="
fd -e test.ts -e spec.ts packages/svelte-table/

echo ""
echo "=== Look for usage examples in README or docs ==="
find packages/svelte-table -name "README*" -o -name "*.md" | xargs grep -l "createTable" 2>/dev/null

Repository: TanStack/table

Length of output: 667


🏁 Script executed:

#!/bin/bash
# Check the installed version and dependencies
echo "=== package.json for svelte-table ==="
cat packages/svelte-table/package.json | grep -A 5 -B 5 "@tanstack/svelte-store"

echo ""
echo "=== Root package.json dependencies ==="
cat package.json | grep -A 20 "dependencies" | grep -i svelte-store

echo ""
echo "=== Look for lockfile entries ==="
grep "@tanstack/svelte-store" pnpm-lock.yaml 2>/dev/null | head -20 || grep "@tanstack/svelte-store" package-lock.json 2>/dev/null | head -20 || echo "No lockfile match"

Repository: TanStack/table

Length of output: 934


🏁 Script executed:

#!/bin/bash
# Re-examine subscribe.ts overloads more carefully and check createTable signature
echo "=== subscribe.ts function signatures (lines 1-35) ==="
head -35 packages/svelte-table/src/subscribe.ts

echo ""
echo "=== createTable.svelte.ts function signature and selector parameter ==="
head -50 packages/svelte-table/src/createTable.svelte.ts | tail -30

Repository: TanStack/table

Length of output: 2173


🏁 Script executed:

#!/bin/bash
# Search for any imports or references to understand how svelte-store's useSelector actually works
echo "=== Check if there's any type annotation or overload documentation ==="
grep -r "useSelector" packages/svelte-table/src/ -A 2 -B 2

echo ""
echo "=== Search the entire repo for how useSelector is used elsewhere ==="
rg "useSelector\(" --type ts -A 2 -B 1 | head -40

echo ""
echo "=== Check createTable integration tests or examples ==="
find . -name "*.test.*" -o -name "*.spec.*" | xargs grep -l "createTable" 2>/dev/null | head -5

Repository: TanStack/table

Length of output: 5021


🌐 Web query:

@tanstack/svelte-store 0.12.0 useSelector undefined selector implementation

💡 Result:

In @tanstack/svelte-store, “useSelector” does exist and it does not take a missing/undefined “selector” argument—you pass a selector function as the 2nd argument, and you read the selected value from the returned object’s .current. The API shape is: - useSelector(source, selector, options) - selector: (state) => TSelected - return: object with readonly current: TSelected [1] Example usage from the official docs: - const count = useSelector(counterStore, (state) => state.count) and then count.current [1] So if you’re seeing “useSelector undefined” or “selector undefined”, the most likely issue is that your call site is wrong (e.g., you’re calling a different hook like useStore/useAtom, importing from the wrong package/version, or calling useSelector with only one argument so the required selector parameter is missing). The docs also show useStore(store, selector?) where selector is optional, but useSelector(source, selector, ...) is described with selector present in the signature and return type uses .current [2].

Citations:


Add defensive guard for optional selector before calling useSelector

selector is optional but line 91 passes it directly to useSelector(table.store, selector) without a fallback. Other adapters (vue-table, solid-table) explicitly guard the selector with conditional logic before invoking useSelector:

const selected = props.selector
  ? useSelector(table.store, props.selector)
  : undefined

Svelte-table should follow the same defensive pattern to ensure table.state is properly initialized even when no selector is provided:

Suggested fix
-  const stateStore = useSelector(table.store, selector)
+  const stateStore = useSelector(
+    table.store,
+    selector ?? ((state) => state as unknown as TSelected),
+  )
🤖 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/svelte-table/src/createTable.svelte.ts` around lines 36 - 37, The
optional selector parameter should be guarded before calling useSelector: change
the code around the creation of selected in createTable (where
useSelector(table.store, selector) is called) to only invoke useSelector when
selector is truthy and otherwise set selected to undefined (matching the pattern
used in other adapters), ensuring table.state/state initialization isn't
dependent on a missing selector; locate the useSelector(table.store, selector)
call and replace it with a conditional that checks selector first and assigns
selected accordingly.

@KevinVandy KevinVandy merged commit c5c781a into alpha May 8, 2026
4 of 5 checks passed
@KevinVandy KevinVandy deleted the react-default-selector branch May 8, 2026 17:37
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