Skip to content

Commit ff325a0

Browse files
committed
feat: implement fine-grained rerendering control with an async Lit directive
An async Lit directe `subscribe` is implemented to allow for fine-grained reactivity and rerendering of templates. To support this, `@tanstack/lit-store` is integrated.
1 parent 2e5d3fe commit ff325a0

16 files changed

Lines changed: 892 additions & 113 deletions

File tree

docs/framework/lit/guide/table-state.md

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ There are two different questions when reading table state:
7171
- Do you only need the current value?
7272
- Or should the Lit host update when that value changes?
7373

74-
Use a direct atom or store read for the current value. Use `table.state` or `table.Subscribe` in render output when the host should reflect selected table state.
74+
Use a direct atom or store read for the current value. Use `table.state` or `table.subscribe` in render output when the host should reflect selected table state.
7575

7676
#### Reading State Without Subscribing
7777

@@ -89,7 +89,7 @@ const tableState = table.store.state
8989
const pagination = table.store.state.pagination
9090
```
9191

92-
These reads are current-value reads. The `TableController` handles host invalidation through its subscriptions to the table store and options store. If the UI needs to stay reactive to table state changes, use `table.state`, `table.Subscribe`, or a TanStack Store subscription.
92+
These reads are current-value reads. The `TableController` handles host invalidation through its subscriptions to the table store and options store. If the UI needs to stay reactive to table state changes, use `table.state`, `table.subscribe`, or a TanStack Store subscription.
9393

9494
#### Reading Reactive State with TableController
9595

@@ -113,22 +113,28 @@ const table = this.tableController.table(
113113
table.state.pagination
114114
```
115115

116-
#### Selecting State with table.Subscribe
116+
#### Selecting State with table.subscribe
117117

118-
Use `table.Subscribe` in templates to select a slice of table state while rendering.
118+
Use `table.subscribe` in templates to select a slice of table state while rendering.
119119

120120
```ts
121-
${table.Subscribe({
122-
selector: (state) => ({
123-
pagination: state.pagination,
124-
}),
125-
children: ({ pagination }) => html`
121+
private paginationSelector = (state) => ({
122+
pagination: state.pagination,
123+
})
124+
125+
${table.subscribe(
126+
table.store,
127+
this.paginationSelector,
128+
({ pagination }) => html`
126129
<span>Page ${pagination.pageIndex + 1}</span>
127130
`,
128-
})}
131+
)}
129132
```
130133

131-
`table.Subscribe` can also accept a `source`, but in the current Lit adapter host invalidation is wired through the full `table.store` subscription. Treat source mode as a render-time selection convenience, not a guarantee of source-only host invalidation.
134+
The template will only be evaluated when the selected state slice changes. The `table.subscribe` API is useful for fine-grained reactivity in templates.
135+
It is important to provide a stable reference for the selector function to avoid unnecessary re-renders. You can define the selector as a class property or method to ensure it remains stable across renders.
136+
137+
132138

133139
### Setting Table State
134140

docs/framework/lit/lit-table.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,26 @@ const columns: Array<ColumnDef<typeof features, Person>> = [
3939
export class PeopleTable extends LitElement {
4040
private tableController = new TableController<typeof features, Person>(this)
4141

42+
private table = this.tableController.table({
43+
features,
44+
rowModels: {},
45+
columns,
46+
data: this.data,
47+
})
48+
4249
@state()
4350
private data: Person[] = []
4451

52+
protected updated(changedProperties: Map<string, unknown>) {
53+
if (changedProperties.has('data')) {
54+
this.table.setOption((prev) => ({
55+
...prev,
56+
data: this.data,
57+
}))
58+
}
59+
}
60+
4561
protected render() {
46-
const table = this.tableController.table({
47-
features,
48-
rowModels: {},
49-
columns,
50-
data: this.data,
51-
})
5262

5363
return html`...`
5464
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Example: Basic Subscribe
2+
3+
This example demonstrates fine-grained state subscriptions using `SubscribeController` for optimized rendering in Lit.
4+
5+
**Key Features:**
6+
7+
- **Fine-grained subscriptions**: Each part of the table subscribes only to the state it needs
8+
- **SubscribeController**: A new Lit ReactiveController that handles store/atom subscriptions declaratively
9+
- **External atoms**: State management via `createAtom` from `@tanstack/store` for full control
10+
- **Performance optimized**: Only affected UI elements re-render when their subscribed state changes
11+
12+
**What this shows:**
13+
14+
1. Global filter input that re-renders only when global filter changes
15+
2. Table rows that re-render only when filtering/pagination state changes
16+
3. Pagination controls that re-render only when pagination state changes
17+
4. Row selection summary that re-renders only when selection changes
18+
5. Full table state display for debugging
19+
20+
This pattern is ideal for large tables where you want to minimize unnecessary re-renders and have precise control over which components subscribe to which state slices.
21+
22+
## Running the Example
23+
24+
To run this example:
25+
26+
- `npm install` or `yarn`
27+
- `npm run start` or `yarn start`
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>TanStack Lit Table - Basic Subscribe</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.ts"></script>
11+
<lit-table-example></lit-table-example>
12+
</body>
13+
</html>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "tanstack-lit-table-example-basic-subscribe",
3+
"private": true,
4+
"scripts": {
5+
"dev": "vite",
6+
"build": "vite build",
7+
"serve": "vite preview",
8+
"start": "vite",
9+
"lint": "eslint ./src"
10+
},
11+
"dependencies": {
12+
"@faker-js/faker": "^10.4.0",
13+
"@tanstack/lit-store": "^0.13.2",
14+
"@tanstack/lit-table": "^9.0.0-beta.6",
15+
"lit": "^3.3.3"
16+
},
17+
"devDependencies": {
18+
"@rollup/plugin-replace": "^6.0.3",
19+
"typescript": "6.0.3",
20+
"vite": "^8.0.16"
21+
}
22+
}

0 commit comments

Comments
 (0)