@lankajs/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/chunk-7D4SUZUM.js +38 -0
- package/dist/chunk-7D4SUZUM.js.map +1 -0
- package/dist/index.d.ts +173 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +43 -0
- package/dist/testing.js +45702 -0
- package/dist/testing.js.map +1 -0
- package/package.json +56 -0
- package/skills/lanka-react/SKILL.md +160 -0
- package/skills/lanka-react/reference.md +262 -0
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lankajs/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "module: One hook — `useLankaVM` — and the access tracking core already does.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/lankajs/lanka.git",
|
|
10
|
+
"directory": "modules/bindings/react"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/lankajs/lanka/tree/main/modules/bindings/react#readme",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./testing": {
|
|
21
|
+
"types": "./dist/testing.d.ts",
|
|
22
|
+
"default": "./dist/testing.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"README.md",
|
|
30
|
+
"skills"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"lanka": "^2.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@testing-library/react": "^16.3.0",
|
|
37
|
+
"@lankajs/tool-testing": "^2.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": "^19.2.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@testing-library/react": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"lint": "eslint src _playground --max-warnings=0",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:coverage": "vitest run --coverage",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"bench": "vitest bench --run",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lanka-react
|
|
3
|
+
description: Read a lanka ViewModel from a React component with useLankaVM, keep the useTodoVM() hook spelling with toLankaReactVM, and stop a selector from repainting for changes it did not pick with useLankaShallow. Use when writing or reviewing a React screen in a lanka application, when a component does not repaint after state changed, when a selector repaints a screen for changes it did not select, when deciding what a server component may read, or when reviewing code that imports `@lankajs/react`.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: lankajs
|
|
7
|
+
package: @lankajs/react
|
|
8
|
+
version: "0.1.0"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# @lankajs/react
|
|
12
|
+
|
|
13
|
+
One call to read a ViewModel, two spellings, and one wrapper that makes a
|
|
14
|
+
selector mean something. `reference.md` beside this file is the full guide.
|
|
15
|
+
|
|
16
|
+
> [!NOTE]
|
|
17
|
+
> Only what the framework or a gate refuses is binding. Everything else here is a
|
|
18
|
+
> recommendation you can adapt.
|
|
19
|
+
|
|
20
|
+
## Pick the call
|
|
21
|
+
|
|
22
|
+
| The situation | Use |
|
|
23
|
+
| ------------------------------------------ | ----------------------------------------------------- |
|
|
24
|
+
| a component reads a ViewModel | `useLankaVM(todoVM)` |
|
|
25
|
+
| it needs one derived value | `useLankaVM(todoVM, (s) => s.todos.length)` |
|
|
26
|
+
| the selector builds an **object or array** | `useLankaVM(todoVM, useLankaShallow((s) => ({ … })))` |
|
|
27
|
+
| the codebase already writes `useTodoVM()` | `toLankaReactVM(todoVM)`, once per file |
|
|
28
|
+
| outside a component — a handler, a module | `todoVM.getState()` |
|
|
29
|
+
| a component test | `renderWithLanka` from `@lankajs/react/testing` |
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { useLankaVM } from "@lankajs/react";
|
|
33
|
+
import { todoVM } from "./todoVM";
|
|
34
|
+
|
|
35
|
+
export const TodoScreen = () => {
|
|
36
|
+
const { todos, isLoading, load } = useLankaVM(todoVM);
|
|
37
|
+
|
|
38
|
+
if (isLoading) return <p>loading</p>;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<ul onClick={() => void load()}>
|
|
42
|
+
{todos.map((t) => (
|
|
43
|
+
<li key={t.id}>{t.title}</li>
|
|
44
|
+
))}
|
|
45
|
+
</ul>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
It answers **the state itself** — React's own idea of reactivity, which is the
|
|
51
|
+
one thing the shelf does not make uniform.
|
|
52
|
+
|
|
53
|
+
## The selector that repaints for everything
|
|
54
|
+
|
|
55
|
+
`useLankaVM(vm, (s) => ({ a: s.a }))` is the commonest thing a React reader
|
|
56
|
+
writes. It is safe — the binding runs a selector once per state object and holds
|
|
57
|
+
the answer — but on its own it wakes the component for **every** change in the
|
|
58
|
+
ViewModel, including the keys the selector exists to ignore: a fresh object is
|
|
59
|
+
new whenever the state is new.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { useLankaShallow, useLankaVM } from "@lankajs/react";
|
|
63
|
+
|
|
64
|
+
const { title, status } = useLankaVM(
|
|
65
|
+
missionVM,
|
|
66
|
+
useLankaShallow((s) => ({ title: s.title, status: s.status })),
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A selector answering a **primitive** never needed it, which is what makes the
|
|
71
|
+
cost quiet: the shape that is free and the shape that repaints on everything
|
|
72
|
+
look the same on the page. Wrap every selector whose answer is an object or an
|
|
73
|
+
array.
|
|
74
|
+
|
|
75
|
+
Before the binding held the selection, an unwrapped object selector **crashed on
|
|
76
|
+
the first paint** with "Maximum update depth exceeded". If you meet that message,
|
|
77
|
+
you are on a version older than this one.
|
|
78
|
+
|
|
79
|
+
## The hook spelling, for a codebase that has it
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { toLankaReactVM } from "@lankajs/react";
|
|
83
|
+
|
|
84
|
+
export const useFAQViewModel = toLankaReactVM(faqVM);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
const supportLink = useFAQViewModel((state) => state.supportLink);
|
|
89
|
+
useFAQViewModel.getState().trackSupportContacted("faq"); // in a handler
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
One wrapper per ViewModel file, no call site touched. It works on any ViewModel
|
|
93
|
+
however it was built, wraps ONE store, adds no state, and laziness survives —
|
|
94
|
+
reading `useFAQViewModel.name` constructs nothing.
|
|
95
|
+
|
|
96
|
+
Which spelling is taste, with one thing to weigh: `useLankaVM(todoVM)` is what
|
|
97
|
+
the other four bindings publish, so a screen written that way moves between
|
|
98
|
+
frameworks unedited.
|
|
99
|
+
|
|
100
|
+
## What re-renders
|
|
101
|
+
|
|
102
|
+
Without a selector the value RECORDS which keys you read, and the next change
|
|
103
|
+
repaints only if one of those moved. With a selector, the selector decides and
|
|
104
|
+
tracking is bypassed.
|
|
105
|
+
|
|
106
|
+
> [!WARNING]
|
|
107
|
+
> **The blind spot.** Tracking sees keys you read DIRECTLY. A key reached only
|
|
108
|
+
> inside a derived getter is invisible to it, so a change to that key repaints
|
|
109
|
+
> nothing and the screen freezes with no error. Set
|
|
110
|
+
> `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT read the
|
|
111
|
+
> underlying keys in the view "for the side effect": that is dead code, and a
|
|
112
|
+
> refactor or a lint autofix removes it. In development the framework announces
|
|
113
|
+
> the mismatch by ViewModel and key name.
|
|
114
|
+
|
|
115
|
+
## Server components
|
|
116
|
+
|
|
117
|
+
`lanka/viewmodel` carries no `"use client"`, so a server component may read
|
|
118
|
+
`todoVM.getState()`. This barrel does carry it, because `useLankaVM` is a hook —
|
|
119
|
+
so a server component reads, and only what RENDERS is a client component.
|
|
120
|
+
|
|
121
|
+
## Testing
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { renderWithLanka } from "@lankajs/react/testing";
|
|
125
|
+
|
|
126
|
+
renderWithLanka(<TodoScreen />, {
|
|
127
|
+
fakes: { gateways: { TodoGateway: { list: () => Promise.resolve([]) } } },
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
It takes an **element**, not a component — React Testing Library's own shape.
|
|
132
|
+
Every call gets a fresh instance and disposes the previous one.
|
|
133
|
+
|
|
134
|
+
## Never do these
|
|
135
|
+
|
|
136
|
+
- **Never pass an object-returning selector without `useLankaShallow`.** The
|
|
137
|
+
screen then repaints for every change in the ViewModel, selector or no selector.
|
|
138
|
+
- **Never call `useLankaVM` outside a component.** It is a hook; use
|
|
139
|
+
`todoVM.getState()` in a handler or a module.
|
|
140
|
+
- **Never import this barrel from a server component.** It carries
|
|
141
|
+
`"use client"`; read the ViewModel's state directly instead.
|
|
142
|
+
- **Never subscribe by hand to "fix" the blind spot.** Turn the optimization off
|
|
143
|
+
on the ViewModel — that is the switch built for it.
|
|
144
|
+
- **Never look for a `stop()`.** React releases the subscription on unmount; the
|
|
145
|
+
other bindings publish one because their call can happen outside a scope.
|
|
146
|
+
|
|
147
|
+
## Symptom → cause
|
|
148
|
+
|
|
149
|
+
| What you see | What it is |
|
|
150
|
+
| ------------------------------------------------- | -------------------------------------------- |
|
|
151
|
+
| a screen repainting for changes it never selected | an object selector without `useLankaShallow` |
|
|
152
|
+
| the screen never updates, no error | the tracking blind spot — a derived getter |
|
|
153
|
+
| "Invalid hook call" | `useLankaVM` outside a component |
|
|
154
|
+
| a build error about `"use client"` | this barrel imported from a server component |
|
|
155
|
+
| a test sees the previous test's state | a render that bypassed `renderWithLanka` |
|
|
156
|
+
|
|
157
|
+
## More
|
|
158
|
+
|
|
159
|
+
`reference.md` — the full guide: the tracking rules, the callable spelling in
|
|
160
|
+
detail, and what this package deliberately is not.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<!-- Generated from modules/bindings/react/GUIDE.md by scripts/skills.mjs. Edit the guide. -->
|
|
2
|
+
|
|
3
|
+
> **`@lankajs/react@0.1.0`** — this document describes that version.
|
|
4
|
+
>
|
|
5
|
+
> Install: `npm install @lankajs/react react zustand` (the peers are not optional; only npm adds a missing one for you).
|
|
6
|
+
>
|
|
7
|
+
> Complete code, compiled and run in CI: [modules/bindings/react/_playground/playground.test.tsx](https://github.com/lankajs/lanka/blob/main/modules/bindings/react/_playground/playground.test.tsx)
|
|
8
|
+
|
|
9
|
+
# @lankajs/react — user guide
|
|
10
|
+
|
|
11
|
+
How a React component reads a lanka ViewModel.
|
|
12
|
+
|
|
13
|
+
## You will learn
|
|
14
|
+
|
|
15
|
+
- the one call this package publishes, and what it answers
|
|
16
|
+
- how to keep React's familiar `useTodoVM()` spelling, if you had it
|
|
17
|
+
- why a selector that returns an object needs `useLankaShallow`, and what happens without it
|
|
18
|
+
- when a component re-renders and when it deliberately does not
|
|
19
|
+
- what to do about a ViewModel that derives what the screen shows
|
|
20
|
+
- how to test a React component with a live framework behind it
|
|
21
|
+
|
|
22
|
+
## When to reach for this
|
|
23
|
+
|
|
24
|
+
Reach for it the moment a React component has to read a lanka ViewModel — that
|
|
25
|
+
is the whole job, and there is no other supported way to do it. Install this one
|
|
26
|
+
package and no other binding: one application installs one, and `@lankajs/react`
|
|
27
|
+
serves React Native too.
|
|
28
|
+
|
|
29
|
+
You do NOT need it to reach the rest of the framework. Gateways, scenarios and
|
|
30
|
+
the locator are plain calls with no view in them, and `viewModel.getState()`
|
|
31
|
+
works anywhere, including on a server.
|
|
32
|
+
|
|
33
|
+
> [!NOTE]
|
|
34
|
+
> Everything below is how this package is _meant_ to be used, not how it must
|
|
35
|
+
> be. The framework bends at the seams it publishes — see
|
|
36
|
+
> [ARCHITECTURE.md](https://github.com/lankajs/lanka/blob/main/ARCHITECTURE.md) for what is checked and what is
|
|
37
|
+
> merely advice.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @lankajs/react react zustand
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> [!IMPORTANT]
|
|
46
|
+
> `react` is already in your project; `zustand` is `lanka`'s own peer. npm adds
|
|
47
|
+
> a missing peer for you and pnpm does not, so the line names all of them.
|
|
48
|
+
|
|
49
|
+
## The one call
|
|
50
|
+
|
|
51
|
+
`useLankaVM` is a hook. Every member of `modules/bindings/` publishes that same
|
|
52
|
+
name, so moving a screen from one framework to another rewrites the view and not
|
|
53
|
+
the vocabulary.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { useLankaVM } from "@lankajs/react";
|
|
57
|
+
import { todoVM } from "./todoVM";
|
|
58
|
+
|
|
59
|
+
export const TodoScreen = () => {
|
|
60
|
+
const { todos, isLoading, load } = useLankaVM(todoVM);
|
|
61
|
+
|
|
62
|
+
if (isLoading) return <p>loading</p>;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<ul onClick={() => void load()}>
|
|
66
|
+
{todos.map((todo) => (
|
|
67
|
+
<li key={todo.id}>{todo.title}</li>
|
|
68
|
+
))}
|
|
69
|
+
</ul>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
It answers **the state itself** — the one thing this shelf does not make uniform,
|
|
75
|
+
because that is React's own idea of reactivity and a binding that hid it
|
|
76
|
+
would be a second reactivity system fighting the first.
|
|
77
|
+
|
|
78
|
+
## The React spelling, if you prefer it
|
|
79
|
+
|
|
80
|
+
Until 2.0 a ViewModel WAS a hook: `createLankaVM` answered a callable, and every
|
|
81
|
+
screen called it. Core cannot do that any more — it may not know what a hook is —
|
|
82
|
+
but this package may, and it does:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { toLankaReactVM } from "@lankajs/react";
|
|
86
|
+
import { createLazyLankaVM } from "lanka/viewmodel";
|
|
87
|
+
|
|
88
|
+
export const useFAQViewModel = toLankaReactVM(
|
|
89
|
+
createLazyLankaVM<IFAQState, IFAQActions>({ … }),
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
const supportLink = useFAQViewModel((state) => state.supportLink);
|
|
95
|
+
const { supportLink, fetchSupportLink } = useFAQViewModel();
|
|
96
|
+
useFAQViewModel.getState().trackSupportContacted("faq"); // in a handler, as always
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
That is the entire migration for a codebase on 1.x: one wrapper per ViewModel
|
|
100
|
+
file, and not one call site touched.
|
|
101
|
+
|
|
102
|
+
It works on any ViewModel, however it was built — the factory, the lazy factory,
|
|
103
|
+
`ALankaVM`'s `build()`, a shared-store ViewModel — and it wraps ONE store: the
|
|
104
|
+
call forwards to `useLankaVM` and every member forwards to the ViewModel, so
|
|
105
|
+
notification, access tracking and lazy construction are the ones documented
|
|
106
|
+
below. A ViewModel read through this and the same one read in Vue answer
|
|
107
|
+
identically.
|
|
108
|
+
|
|
109
|
+
**Laziness survives.** A lazily declared ViewModel still builds on first use:
|
|
110
|
+
reading `useFAQViewModel.name` answers from the config and constructs nothing,
|
|
111
|
+
and `dispose` is still there.
|
|
112
|
+
|
|
113
|
+
Which spelling to use is taste, with one thing to weigh: `useLankaVM(todoVM)` is
|
|
114
|
+
what the other four bindings publish, so a screen written that way moves between
|
|
115
|
+
frameworks unedited. `toLankaReactVM` is for a React codebase that already has
|
|
116
|
+
hundreds of `useTodoVM()` call sites, and for one that simply prefers them.
|
|
117
|
+
|
|
118
|
+
## What re-renders, and what does not
|
|
119
|
+
|
|
120
|
+
Without a selector you get a value that RECORDS which keys you read. The next
|
|
121
|
+
change re-renders only if one of those moved:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// reads `todos`; a change to `isLoading` alone repaints nothing
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
With a selector, the selector decides and tracking is bypassed:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const count = useLankaVM(todoVM, (state) => state.todos.length);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
> [!WARNING]
|
|
134
|
+
> **The blind spot.** Tracking sees keys you read DIRECTLY. A key reached only
|
|
135
|
+
> inside a derived getter — an action calling `get()` — is invisible to it, so a
|
|
136
|
+
> change to that key re-renders nothing and the screen freezes with no error.
|
|
137
|
+
>
|
|
138
|
+
> Set `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT patch
|
|
139
|
+
> it in the view by reading the underlying keys "for the side effect": that is
|
|
140
|
+
> dead code, and a refactor or a lint autofix removes it.
|
|
141
|
+
>
|
|
142
|
+
> In development the framework announces the mismatch by ViewModel and key name.
|
|
143
|
+
|
|
144
|
+
## A selector that returns an object
|
|
145
|
+
|
|
146
|
+
`useLankaVM(vm, (state) => ({ … }))` is the commonest thing a React reader
|
|
147
|
+
writes, and it is safe: the binding runs a selector once per state object and
|
|
148
|
+
holds the answer, so the two snapshot reads of one commit see the same
|
|
149
|
+
reference.
|
|
150
|
+
|
|
151
|
+
What it does NOT get on its own is the thing you took a selector for. A fresh
|
|
152
|
+
object is new whenever the state is new, so the component wakes for **every**
|
|
153
|
+
change in the ViewModel — including the keys your selector exists to ignore.
|
|
154
|
+
`useLankaShallow` is the comparison that makes the selection mean something:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
import { useLankaShallow, useLankaVM } from "@lankajs/react";
|
|
158
|
+
|
|
159
|
+
const { title, status } = useLankaVM(
|
|
160
|
+
missionVM,
|
|
161
|
+
useLankaShallow((state) => ({ title: state.title, status: state.status })),
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
It compares the selection one level deep — own keys, same count, `Object.is` on
|
|
166
|
+
each value, arrays included — and hands back the previous object when nothing in
|
|
167
|
+
it moved.
|
|
168
|
+
|
|
169
|
+
The comparison itself is `createLankaShallowHold` in `lanka/viewmodel`, and every
|
|
170
|
+
binding on the shelf can reach it. React gets a hook over it because React needs
|
|
171
|
+
one: a component re-runs `useLankaShallow` on every render, so the holding has to
|
|
172
|
+
survive a render while the selector stays the current one — which is what makes a
|
|
173
|
+
selection computed from props safe here. Use the hook; the core name is what the
|
|
174
|
+
other four bindings write.
|
|
175
|
+
|
|
176
|
+
A selector answering a PRIMITIVE never needed it, which is what makes the cost
|
|
177
|
+
quiet: the shape that is free and the shape that repaints on everything look the
|
|
178
|
+
same on the page. So the rule is simple — **wrap every selector whose answer is
|
|
179
|
+
an object or an array**, and leave the rest alone.
|
|
180
|
+
|
|
181
|
+
> [!NOTE]
|
|
182
|
+
> This used to be worse. Until the binding held the selection, an unwrapped
|
|
183
|
+
> object selector **crashed on the first paint** with "Maximum update depth
|
|
184
|
+
> exceeded" and a stack pointing at React rather than at your selector. If you
|
|
185
|
+
> are reading that message in an older version, this is what it was.
|
|
186
|
+
|
|
187
|
+
<details><summary><b>Deep dive:</b> why a wrapper and not an equality argument</summary>
|
|
188
|
+
|
|
189
|
+
`useLankaVM(vm, selector, isEqual)` was the other option, and it puts the
|
|
190
|
+
comparison in the binding for every caller — including the ones whose selection
|
|
191
|
+
is a string and would pay for a comparison they cannot fail. A wrapper is opt in
|
|
192
|
+
at the call site, which is also where a reader can see it. The shape is React's
|
|
193
|
+
own: a hook returning a selector, so a consumer arriving from zustand has typed
|
|
194
|
+
`useShallow` already.
|
|
195
|
+
|
|
196
|
+
</details>
|
|
197
|
+
## Releasing the subscription
|
|
198
|
+
|
|
199
|
+
React releases the subscription when the component unmounts, and there is
|
|
200
|
+
nothing for you to call: a hook cannot be used outside a component, so the case
|
|
201
|
+
the other bindings publish a `stop` for cannot arise here.
|
|
202
|
+
|
|
203
|
+
## Server components
|
|
204
|
+
|
|
205
|
+
`lanka/viewmodel` carries no `"use client"` — a ViewModel is a store, and a
|
|
206
|
+
server component may read its state with `todoVM.getState()`. This package's
|
|
207
|
+
barrel does carry the directive, because `useLankaVM` is a hook. So the split is
|
|
208
|
+
the useful one: a server component reads, and only what RENDERS is a client
|
|
209
|
+
component.
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
// app/page.tsx — a server component
|
|
213
|
+
import { todoVM } from "./todoVM";
|
|
214
|
+
|
|
215
|
+
export default function Page() {
|
|
216
|
+
return <TodoScreen initial={todoVM.getState().todos} />;
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Testing
|
|
221
|
+
|
|
222
|
+
`@lankajs/react/testing` renders a component with a bootstrapped framework, so a component
|
|
223
|
+
test needs no bootstrap preamble of its own:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
import { renderWithLanka } from "@lankajs/react/testing";
|
|
227
|
+
|
|
228
|
+
renderWithLanka(<TodoScreen />, {
|
|
229
|
+
fakes: { gateways: { TodoGateway: { list: () => Promise.resolve([]) } } },
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
It takes an ELEMENT and not a component, which is React Testing Library's own
|
|
234
|
+
shape — the other four bindings take the component, because theirs do.
|
|
235
|
+
|
|
236
|
+
Every call gets a FRESH instance and disposes the previous one, so a test never
|
|
237
|
+
inherits its neighbour's subscriptions.
|
|
238
|
+
|
|
239
|
+
## What this package is not
|
|
240
|
+
|
|
241
|
+
It is a subscription and a render trigger, and nothing else. The recording of
|
|
242
|
+
which keys you read, the comparison that decides whether a change is worth a
|
|
243
|
+
render, and the blind-spot warning are all in `lanka` itself — which is why the
|
|
244
|
+
behaviour you see is the framework's rather than this package's reading of it,
|
|
245
|
+
and why `lankaViewBindingConformance` can hold every binding to one list.
|
|
246
|
+
|
|
247
|
+
If this package ever needs more than the ViewModel port gives it, the port has
|
|
248
|
+
the defect and the fix belongs in `lanka`, for every framework at once.
|
|
249
|
+
|
|
250
|
+
## Recap
|
|
251
|
+
|
|
252
|
+
- `useLankaVM(todoVM)` is the one call, and every binding publishes that name — a screen written with it moves between frameworks unedited.
|
|
253
|
+
- Without a selector you get a value that records which keys you read, and only those keys re-render you.
|
|
254
|
+
- A key reached only through a derived getter is invisible to tracking: set `enableAccessTrackingOptimization: false` on that ViewModel rather than patching the view.
|
|
255
|
+
- Wrap every selector whose answer is an object or an array in `useLankaShallow`; a primitive selector needs nothing.
|
|
256
|
+
- `toLankaReactVM` hands back the `useTodoVM()` spelling for a 1.x codebase, over the same store and with no behaviour of its own.
|
|
257
|
+
- The barrel is `"use client"` and `lanka/viewmodel` is not, so a server component may read state and only what renders it is a client component.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
Maintaining this package: [SKILL.md](https://github.com/lankajs/lanka/blob/main/modules/bindings/react/SKILL.md) · What it is:
|
|
262
|
+
[README.md](https://github.com/lankajs/lanka/blob/main/modules/bindings/react/README.md) · Repository map: [../../../README.md](https://github.com/lankajs/lanka/blob/main/README.md)
|