Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/mighty-pans-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/solid-table': patch
---

Deprecate table.Subscribe in the Solid adapter. Read table APIs or atoms directly inside JSX, createMemo, or createEffect; Solid tracks these reads natively. Keep the wrapper for compatibility and update examples and guidance to use direct reads.
5 changes: 5 additions & 0 deletions .changeset/whole-horses-sink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/vue-table': patch
---

Deprecate table.Subscribe in the Vue adapter. Read table APIs or atoms in native Vue reactive contexts, and use a child component when a separate render boundary is needed. Keep the wrapper for compatibility and update migration guidance.
2 changes: 1 addition & 1 deletion docs/framework/solid/guide/column-filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Since the column filter state is an array of objects, you can have multiple colu

#### Accessing Column Filter State

In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.columnFilters.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, `createEffect`, or `table.Subscribe`). In event handlers or other untracked code, the same call simply returns the current value.
In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.columnFilters.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, or `createEffect`). In event handlers or other untracked code, the same call simply returns the current value.

```tsx
const table = createTable({
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/solid/guide/global-filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ You can also define your own custom global filter function and pass it directly

### Global Filter State

The `globalFilter` state slice holds the current global filter value, usually a search string (the slice is typed as `any` so custom global filter functions can accept other value shapes). In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.globalFilter.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, `createEffect`, or `table.Subscribe`). In event handlers, the same call simply returns the current value.
The `globalFilter` state slice holds the current global filter value, usually a search string (the slice is typed as `any` so custom global filter functions can accept other value shapes). In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.globalFilter.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, or `createEffect`). In event handlers, the same call simply returns the current value.

If you need access to the global filter state outside of the table, you can own the slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. Atoms preserve fine-grained subscriptions, and the filter value can be used elsewhere (such as in a query key for server-side filtering) without making the table depend on component-local state.

Expand Down
24 changes: 12 additions & 12 deletions docs/framework/solid/guide/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ TanStack Table V9 delivers major performance improvements, hundreds of bug fixes
### 2. State Management Overhaul

- **TanStack Store foundation**: State is backed by TanStack Store atoms with Solid-aware reactivity.
- **Solid-native reads**: Atom reads participate in Solid tracking when called inside JSX, `createMemo`, `createEffect`, or `table.Subscribe`.
- **Solid-native reads**: Atom reads participate in Solid tracking when called inside JSX, `createMemo`, or `createEffect`.
- **External atoms**: Apps can own individual slices with atoms from `@tanstack/solid-store`.

### 3. Type-Safety Improvements
Expand Down Expand Up @@ -295,12 +295,12 @@ Because these methods now live on the prototype, they also do not appear as own

Solid v9 uses table atoms backed by Solid primitives. Prefer narrow atom reads or Solid memos over broad whole-state reads.

| Surface | Use |
| --------------------------- | --------------------------------------------------------------------------------------------- |
| `table.atoms.<slice>.get()` | Narrow reactive reads inside Solid tracking scopes. |
| `table.store.get()` | Current full state snapshot. Use mostly for debug output or intentionally broad dependencies. |
| `table.Subscribe` | A Solid render boundary whose child reads the atoms it needs. |
| `table.baseAtoms.<slice>` | Internal writable atoms. Prefer feature APIs or external atoms. |
| Surface | Use |
| --------------------------- | ---------------------------------------------------------------------------------------------- |
| `table.atoms.<slice>.get()` | Narrow reactive reads inside Solid tracking scopes. |
| `table.store.get()` | Current full state snapshot. Use mostly for debug output or intentionally broad dependencies. |
| `table.Subscribe` | Deprecated compatibility wrapper. Read table APIs or atoms directly in JSX, memos, or effects. |
| `table.baseAtoms.<slice>` | Internal writable atoms. Prefer feature APIs or external atoms. |

### Accessing State

Expand Down Expand Up @@ -335,16 +335,16 @@ Atom reads can also be used directly in JSX:
</span>
```

### Fine-grained Updates with `table.Subscribe`
### Replace `table.Subscribe`

`table.Subscribe` passes `table.atoms` to its child function. As with any Solid component, the child function body runs once and is untracked, so read atoms inside JSX expressions (or thunks called from JSX) for Solid to track them.
`table.Subscribe` is deprecated. It only passes `table.atoms` to its child function and creates no subscription or tracking scope. Remove the wrapper and read atoms directly inside JSX, `createMemo`, or `createEffect`:

```tsx
<table.Subscribe>
{(atoms) => <span>Page {atoms.pagination.get().pageIndex + 1}</span>}
</table.Subscribe>
<span>Page {table.atoms.pagination.get().pageIndex + 1}</span>
```

Solid tracks these reads natively. Component bodies run once, so keep reactive reads inside a tracked scope.

### Controlled State

The v8-style `state` + `on[State]Change` controlled state patterns still work and remain convenient for simple integrations. For new v9 code, prefer owning state slices with external atoms (see [External Atoms](#external-atoms) below), which give you fine-grained subscriptions without mirroring state through signals. If you do control state with signals, use getters in `state` so Solid tracks the current signal values.
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/solid/guide/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ The `pagination` state is an object that contains the following properties:
- `pageIndex`: The current page index (zero-based).
- `pageSize`: The current page size.

In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.pagination.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, `createEffect`, or `table.Subscribe`). In event handlers, the same call simply returns the current value.
In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.pagination.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, or `createEffect`). In event handlers, the same call simply returns the current value.

If you need access to the `pagination` state outside of the table (a server-side query key is the most common case), you can own the slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. Atoms preserve fine-grained subscriptions, and the pagination value can be used in a query key without making the table depend on component-local state.

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/solid/guide/row-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ console.log(table.getFilteredSelectedRowModel().rows) //get filtered client-side
console.log(table.getGroupedSelectedRowModel().rows) //get grouped client-side selected rows
```

In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.rowSelection.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, `createEffect`, or `table.Subscribe`). In event handlers or other untracked code, the same call simply returns the current value.
In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.rowSelection.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, or `createEffect`). In event handlers or other untracked code, the same call simply returns the current value.

> [!NOTE]
> If you are using `manualPagination`, be aware that the `getSelectedRowModel` API will only return selected rows on the current page because table row models can only generate rows based on the `data` that is passed in. Row selection state, however, can contain row ids that are not present in the `data` array just fine.
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/solid/guide/sorting.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Since the sorting state is an array, it is possible to sort by multiple columns

#### Accessing Sorting State

In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.sorting.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, `createEffect`, or `table.Subscribe`). In event handlers or other untracked code, the same call simply returns the current value. `table.store.get()` returns a current full-state snapshot, useful for debugging.
In Solid, the table's state atoms are backed by Solid signals, so `table.atoms.sorting.get()` is a reactive read when called inside a tracked scope (JSX, `createMemo`, or `createEffect`). In event handlers or other untracked code, the same call simply returns the current value. `table.store.get()` returns a current full-state snapshot, useful for debugging.

```tsx
const table = createTable({
Expand Down
66 changes: 21 additions & 45 deletions docs/framework/solid/guide/table-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ There are two different questions when reading table state:
- Do you only need the current value?
- Or should a Solid computation update when that value changes?

Use direct atom reads for slice values. Use `table.store.get()` for the current flat state snapshot. Because Solid table atoms are backed by Solid signals and memos, atom reads participate in Solid dependency tracking when they happen inside JSX, `createMemo(...)`, `createEffect(...)`, or `table.Subscribe`.
Use direct atom reads for slice values. Use `table.store.get()` for the current flat state snapshot. Because Solid table atoms are backed by Solid signals and memos, atom reads participate in Solid dependency tracking when they happen inside JSX, `createMemo(...)`, or `createEffect(...)`.

#### Reading State

Expand Down Expand Up @@ -120,56 +120,32 @@ You can use atom reads directly in JSX too:
</span>
```

#### Fine-grained Updates with table.Subscribe
#### Fine-grained updates

Use `table.Subscribe` when you want a specific part of the Solid tree to create a reactive render boundary. Its child function receives `table.atoms`. As with any Solid component, the child function body runs once and is untracked, so perform atom reads inside JSX expressions or in thunks called from JSX; Solid tracks only those reads.
Read table APIs and atoms directly inside JSX. Solid tracks the reads and updates the expressions that depend on them. For derived values outside JSX, use `createMemo` or an accessor called from JSX.

```tsx
<table.Subscribe>
{(atoms) => {
// a thunk: the reads run (and track) when JSX calls it, not in the body
const rows = () => {
atoms.columnFilters.get()
atoms.globalFilter.get()
atoms.pagination.get()
return table.getRowModel().rows
}

return (
<tbody>
<For each={rows()}>{(row) => <tr>{/* ... */}</tr>}</For>
</tbody>
)
}}
</table.Subscribe>
```
`table.Subscribe` is deprecated. It only passes atoms to its child function and creates no subscription or tracking scope. Remove the wrapper and replace the child function's `atoms` parameter with `table.atoms`.

```tsx
<table.Subscribe>
{(atoms) => (
<tbody>
<For each={table.getRowModel().rows}>
{(row) => {
const isSelected = () => atoms.rowSelection.get()[row.id]

return (
<tr>
<td>
<input
type="checkbox"
checked={!!isSelected()}
onClick={row.getToggleSelectedHandler()}
/>
</td>
</tr>
)
}}
</For>
</tbody>
)}
</table.Subscribe>
<tbody>
<For each={table.getRowModel().rows}>
{(row) => (
<tr>
<td>
<input
type="checkbox"
checked={!!table.atoms.rowSelection.get()[row.id]}
onClick={row.getToggleSelectedHandler()}
/>
</td>
</tr>
)}
</For>
</tbody>
```

Keep reactive reads inside JSX, memos, or effects. Reading an atom into a plain variable in a component body captures its current value and does not track updates.

### Setting Table State

You should almost never need to set table state directly. TanStack Table features expose dedicated APIs for interacting with their state, and those APIs are the safest way to make changes.
Expand Down
5 changes: 3 additions & 2 deletions docs/framework/solid/reference/functions/createTable.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ title: createTable
function createTable<TFeatures, TData>(tableOptions): SolidTable<TFeatures, TData>;
```

Defined in: [createTable.ts:61](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTable.ts#L61)
Defined in: [createTable.ts:64](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTable.ts#L64)

Creates a Solid table instance backed by Solid-aware TanStack Store atoms.

Table APIs and atom reads participate in Solid dependency tracking, so
computations that read a specific slice can update without invalidating
unrelated UI. Use `table.Subscribe` to create atom-tracked render boundaries.
unrelated UI. Read table APIs or atoms inside JSX, `createMemo`, or
`createEffect` to track updates.

## Type Parameters

Expand Down
6 changes: 2 additions & 4 deletions docs/framework/solid/reference/functions/createTableHook.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ title: createTableHook
function createTableHook<TFeatures, TTableComponents, TCellComponents, THeaderComponents>(__namedParameters): CreateTableHookResult<TFeatures, TTableComponents, TCellComponents, THeaderComponents>;
```

Defined in: [createTableHook.tsx:571](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L571)
Defined in: [createTableHook.tsx:569](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L569)

Creates a custom table hook with pre-bound components for composition.

Expand Down Expand Up @@ -81,9 +81,7 @@ const columnHelper = createAppColumnHelper<Person>()
function PaginationControls() {
const table = useTableContext() // TFeatures already known!
return (
<table.Subscribe>
{(atoms) => <span>Page {atoms.pagination.get().pageIndex + 1}</span>}
</table.Subscribe>
<span>Page {table.atoms.pagination.get().pageIndex + 1}</span>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ title: AppTableComponent

Defined in: [createTableHook.tsx:337](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L337)

Component type for AppTable - root wrapper with optional Subscribe
Component type for AppTable - root wrapper that provides table context

## Type Parameters

Expand All @@ -21,7 +21,7 @@ AppTableComponent(props): Element;

Defined in: [createTableHook.tsx:338](https://github.com/TanStack/table/blob/main/packages/solid-table/src/createTableHook.tsx#L338)

Component type for AppTable - root wrapper with optional Subscribe
Component type for AppTable - root wrapper that provides table context

## Parameters

Expand Down
12 changes: 8 additions & 4 deletions docs/framework/solid/reference/type-aliases/SolidTable.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,12 @@ rendering headers, cells, or footers with custom markup. Mirrors the
<table.FlexRender footer={footer} />
```

### Subscribe()
### ~~Subscribe~~

```ts
Subscribe: (props) => JSX.Element;
```

Creates a reactive render boundary. The child function reads the table
atoms it needs, so Solid only tracks those atom reads.

#### Parameters

##### props
Expand All @@ -52,6 +49,13 @@ atoms it needs, so Solid only tracks those atom reads.

`JSX.Element`

#### Deprecated

Read table APIs or `table.atoms` directly inside JSX,
`createMemo`, or `createEffect`. Solid tracks those reads natively.
This compatibility wrapper only passes atoms to its child function;
it does not create a subscription or tracking scope.

## Type Parameters

### TFeatures
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/vue/guide/cell-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The cell selection feature keeps track of spreadsheet-style rectangular selectio

The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs.

- `table.atoms.cellSelection.get()` - returns the current cell selection (reactive inside templates, `computed(...)`, `watch(...)`, and `table.Subscribe`; a plain snapshot elsewhere)
- `table.atoms.cellSelection.get()` - returns the current cell selection (reactive inside templates, `computed(...)`, `watch(...)` sources, and render functions; a plain snapshot elsewhere)
- `getSelectedCellCount()` - returns how many cells are selected
- `getSelectedCellIds()` - returns the ids of every selected cell
- `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/vue/guide/column-faceting.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ If you want each count to represent rows, make sure `getUniqueValues` returns ea

### Reactive Facet Controls in Vue

Read facet APIs inside a Vue reactive context such as `computed`, a template, or `table.Subscribe`. A filter component can derive its options from a stable `column` prop with `computed`.
Read facet APIs inside a Vue reactive context such as `computed`, a template, or a render function. A filter component can derive its options from a stable `column` prop with `computed`.

```vue
<script setup lang="ts">
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/vue/guide/column-filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Since the column filter state is an array of objects, you can have multiple colu

#### Accessing Column Filter State

You can read the column filter state from the table instance with `table.atoms.columnFilters.get()`. Because the Vue adapter backs table atoms with Vue refs and computed values, this read is reactive when it happens inside a template, `computed(...)`, `watch(...)`, or `table.Subscribe`. Outside of a Vue reactive context, it is a plain snapshot of the current value.
You can read the column filter state from the table instance with `table.atoms.columnFilters.get()`. Because the Vue adapter backs table atoms with Vue refs and computed values, this read is reactive when it happens inside a template, `computed(...)`, `watch(...)` sources, or render functions. Outside of a Vue reactive context, it is a plain snapshot of the current value.

```ts
const table = useTable({
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/vue/guide/column-resizing.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const table = useTable({

By default, the column resize mode is set to `"onEnd"`. This means that the `column.getSize()` API will not return the new column size until the user has finished resizing (dragging) the column. Usually a small UI indicator will be displayed while the user is resizing the column.

Vue's reactivity tracks dependencies per component, so a drag that updates the `columnResizing` state on every mouse move re-renders every component whose template reads column sizes. For large or complex tables, the `"onEnd"` column resize mode can be a good default option to avoid stuttering or lagging while the user resizes columns. That is not to say that you cannot achieve 60 fps column resizing renders with the Vue adapter, but you may need to keep size reads scoped (computed values, `table.Subscribe`, or CSS variables) so each drag frame touches as little of the template as possible.
Vue's reactivity tracks dependencies per component, so a drag that updates the `columnResizing` state on every mouse move re-renders every component whose template reads column sizes. For large or complex tables, the `"onEnd"` column resize mode can be a good default option to avoid stuttering or lagging while the user resizes columns. That is not to say that you cannot achieve 60 fps column resizing renders with the Vue adapter, but you may need to keep size reads scoped (computed values, child components, or CSS variables) so each drag frame touches as little of the template as possible.

> Advanced column resizing performance tips will be discussed [down below](#advanced-column-resizing-performance).

Expand Down
Loading
Loading