@jsenv/navi 0.29.19 → 0.29.20
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/dist/jsenv_navi.js +34 -30
- package/dist/jsenv_navi.js.map +4 -4
- package/docs/AI_INSTRUCTIONS.md +4 -0
- package/docs/list_refresh.md +131 -0
- package/docs/resource.md +2 -0
- package/package.json +1 -1
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -33,6 +33,10 @@ consistency across the app, not from any single call site.
|
|
|
33
33
|
- `docs/resource.md` — REST state: `resource()`, relationships, callback return
|
|
34
34
|
contracts, autorerun rules. Companion files: `docs/actions.md`,
|
|
35
35
|
`docs/resource_with_params.md`, `docs/resource_dependencies.md`.
|
|
36
|
+
- `docs/list_refresh.md` — what a write sends back to the network and what stays
|
|
37
|
+
on screen meanwhile: stale data returned by `useAsyncData({ loading: true })`,
|
|
38
|
+
what updates from a response without any request, `rerunOn` and its defaults.
|
|
39
|
+
Read it before adding verbs to `rerunOn` or hiding a list on `loading`.
|
|
36
40
|
- `docs/css_architecture.md` — how Navi's CSS layering works, and the
|
|
37
41
|
supported ways to override component styles (props > CSS variables > direct
|
|
38
42
|
rule overrides, in that preference order).
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# A list while it refreshes
|
|
2
|
+
|
|
3
|
+
When a write touches one item of a list, two questions decide what the user
|
|
4
|
+
sees: **what goes back to the network**, and **what stays on screen meanwhile**.
|
|
5
|
+
Getting either wrong turns "pause one row" into a full page reload.
|
|
6
|
+
|
|
7
|
+
The short answer:
|
|
8
|
+
|
|
9
|
+
- A write that returns the modified item fixes every list containing it, with
|
|
10
|
+
no request and no loading state at all.
|
|
11
|
+
- `useAsyncData(action, { loading: true })` keeps returning the previous data
|
|
12
|
+
while the action re-runs — the list is never taken away unless the component
|
|
13
|
+
throws it away.
|
|
14
|
+
|
|
15
|
+
## `loading: true` returns the previous value
|
|
16
|
+
|
|
17
|
+
`useAsyncData` returns `[data, loading, error]`. During a re-run, `data` is the
|
|
18
|
+
**previous data**, not `undefined`:
|
|
19
|
+
|
|
20
|
+
| Moment | `data` | `loading` |
|
|
21
|
+
| --------------------- | ----------- | --------- |
|
|
22
|
+
| never completed yet | `undefined` | `true` |
|
|
23
|
+
| running after success | previous | `true` |
|
|
24
|
+
| completed | fresh | `false` |
|
|
25
|
+
|
|
26
|
+
So the emptiness test is `data === undefined`, never `loading`:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
const [items, loading] = useAsyncData(ACTION, { loading: true });
|
|
30
|
+
if (items === undefined) {
|
|
31
|
+
return null; // first load: there is nothing to show yet
|
|
32
|
+
}
|
|
33
|
+
return <RadarList radars={items} busy={loading} />; // re-read: stay on screen
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
// ✗ blanks the page on every re-run, for a checkbox on one row
|
|
38
|
+
if (loading) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Read `loading` as "what you are displaying is from before", not "there is
|
|
44
|
+
nothing to display".
|
|
45
|
+
|
|
46
|
+
`<List loading>` is the first-load answer, not the refresh one: it replaces the
|
|
47
|
+
rows with skeletons. Pass it while stale rows exist and they disappear — same
|
|
48
|
+
mistake as `loading ? null :`, one level down.
|
|
49
|
+
|
|
50
|
+
## What updates without a request
|
|
51
|
+
|
|
52
|
+
An action's `data` is a computation over the resource store, not a snapshot of
|
|
53
|
+
its own last response. Any write that puts an item into the store recomputes
|
|
54
|
+
every list holding it, while the `GET_MANY` action stays `COMPLETED` — no
|
|
55
|
+
request, no loading state, no flicker.
|
|
56
|
+
|
|
57
|
+
The condition is the whole hinge of the system:
|
|
58
|
+
|
|
59
|
+
> **the write's callback must return the item, with its key** — `{ id, … }`.
|
|
60
|
+
|
|
61
|
+
Partial props are fine (`{ id, paused: true }` merges into the stored item); the
|
|
62
|
+
key is what cannot be missing. The corollary matters just as much: a callback
|
|
63
|
+
that returns nothing — a `204`, or a `fetch` whose result is dropped — writes
|
|
64
|
+
nothing to the store, and the change never reaches the screen. There, the only
|
|
65
|
+
way back is a re-read.
|
|
66
|
+
|
|
67
|
+
`DELETE` is symmetric: returning the id drops the item from the store, and every
|
|
68
|
+
list containing it drops it too.
|
|
69
|
+
|
|
70
|
+
## `rerunOn`, verb by verb
|
|
71
|
+
|
|
72
|
+
`rerunOn` says which verbs invalidate this resource's `GET` / `GET_MANY`:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
const GAME_RADAR = resource("game_radar", {
|
|
76
|
+
rerunOn: { GET_MANY: ["POST", "DELETE"] },
|
|
77
|
+
…
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Defaults are `{ GET: false, GET_MANY: ["POST"] }`:
|
|
82
|
+
|
|
83
|
+
| Default | Why |
|
|
84
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| `GET: false` | `PUT`/`PATCH` already update the UI through the store; `DELETE` resets the `GET` rather than re-running it, so a deleted item shows nothing instead of a spinner then a 404. Give the deleted case its own UI (an "item not found" panel, a redirect) instead of `GET: ["DELETE"]`. |
|
|
86
|
+
| `GET_MANY: ["POST"]` | Whether a new item belongs in this list depends on filters, pagination, sort — the backend knows, the client does not. `DELETE` is excluded because the store already removes the item from every list. |
|
|
87
|
+
|
|
88
|
+
Adding `PUT`/`PATCH` to `GET_MANY` is the usual over-correction: it costs a
|
|
89
|
+
request and a `loading` pass to obtain something the response already contained.
|
|
90
|
+
When a `PATCH` "doesn't show", the fix is almost always server-side — return the
|
|
91
|
+
updated item — not client-side refreshing.
|
|
92
|
+
|
|
93
|
+
## Decision table
|
|
94
|
+
|
|
95
|
+
| What changed | Re-read the list? |
|
|
96
|
+
| ------------------------- | ---------------------------------------------------- |
|
|
97
|
+
| a field of one item | no — the write's response is enough |
|
|
98
|
+
| membership of the list | yes (`POST`) — the backend decides who belongs |
|
|
99
|
+
| the ORDER of the list | yes — the store stores, it does not sort (see below) |
|
|
100
|
+
| nothing came back (`204`) | yes — there is nothing to put in the store |
|
|
101
|
+
|
|
102
|
+
## The store stores, it does not sort
|
|
103
|
+
|
|
104
|
+
Each `*_MANY` action holds its own array of ids; its data is those ids resolved
|
|
105
|
+
against the store, in that order. A `PUT_MANY` returning the collection in a new
|
|
106
|
+
order updates every item, and sets the order **of the `PUT_MANY` action** — the
|
|
107
|
+
`GET_MANY` list keeps the order it already had.
|
|
108
|
+
|
|
109
|
+
That is the design (a store is not an index), but it has a sharp edge: a
|
|
110
|
+
drag-and-drop that "works" on screen — because local state holds the order —
|
|
111
|
+
comes back in the old order on the next visit. After a reorder, re-read the list
|
|
112
|
+
explicitly:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
await REORDER_ACTION.run({ … });
|
|
116
|
+
GET_MANY_ACTION.rerun();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## `.rerun()`, not `.run()`, to refresh
|
|
120
|
+
|
|
121
|
+
`.run()` on an action that already `COMPLETED` does nothing: it is a request to
|
|
122
|
+
have the data, and the data is there. Wiring a "check now" button to `.run()`
|
|
123
|
+
therefore checks nothing, silently. Use `.rerun()`, which resets the action and
|
|
124
|
+
runs it again.
|
|
125
|
+
|
|
126
|
+
## See also
|
|
127
|
+
|
|
128
|
+
- [resource.md](./resource.md) — `resource()`, relations, callback return
|
|
129
|
+
contracts
|
|
130
|
+
- [resource_dependencies.md](./resource_dependencies.md) — invalidating a
|
|
131
|
+
resource from another one
|
package/docs/resource.md
CHANGED
|
@@ -264,4 +264,6 @@ the actions this mutation should invalidate. See [actions.md](./actions.md).
|
|
|
264
264
|
isolated lifecycles
|
|
265
265
|
- [resource_dependencies.md](./resource_dependencies.md) — cross-resource
|
|
266
266
|
autorerun
|
|
267
|
+
- [list_refresh.md](./list_refresh.md) — what re-runs after a write, and what
|
|
268
|
+
stays on screen while it does
|
|
267
269
|
- [actions.md](./actions.md) — action lifecycle, `bindParams`, `useAsyncData`
|