@jsenv/navi 0.29.225 → 0.29.226

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.
@@ -115,12 +115,13 @@ to start when unsure which export solves a problem.
115
115
  - `offline.md` — `setNetworkPolicy`: answer from the store, ask nothing, refuse
116
116
  writes politely. Read before caching responses in the app.
117
117
  - `dynamic_import.md` — a screen's code arrives like its data: a page's
118
- import is a `routeAction`, a component reads its own with
119
- `useAsyncData(() => import(…))`, so `<Loading>`, `<ErrorBoundary>` and
120
- `rerun` cover it; links prerun on intent what asks nothing of the address; a
121
- transition photographs the fallback; a chunk's `import.meta.css`; a chunk
122
- that does not come is never a bug; why not preact's `lazy`. Read before an
123
- `import()` in a component or a page, and before `prefetch={false}`.
118
+ import is a `routeAction` read with `loading: true` (a branch, not a
119
+ `<Loading>`), a component reads its own with `useAsyncData(() => import(…))`;
120
+ links prerun on intent what asks nothing of the address; a transition
121
+ photographs the pending screen; a chunk's `import.meta.css`; a chunk that
122
+ does not come is never a bug and never comes twice in one document — offer
123
+ `reload()`; why not preact's `lazy`. Read before an `import()` in a component
124
+ or a page, and before `prefetch={false}`.
124
125
 
125
126
  ### Controls and forms
126
127
 
@@ -14,7 +14,9 @@ a screen already reads its data with. Two shapes, decided by who asks.
14
14
 
15
15
  The address asks for the page's data through a route action; it asks for the
16
16
  page's code the same way, and the two are in flight together from the url
17
- change:
17
+ change. The page component in the entry reads the code like data — with
18
+ `loading: true` and `error: true`, drawing the page's own pending screen and
19
+ its own unavailable screen where the page will stand:
18
20
 
19
21
  ```jsx
20
22
  const GAME_PAGE_ACTION = routeAction(GAME_ROUTE, GAME.GET, () => ({
@@ -25,33 +27,46 @@ const GAME_PAGE_CODE = routeAction(GAME_ROUTE, () =>
25
27
  );
26
28
 
27
29
  const GamePage = () => {
28
- const [Page] = useAsyncData(GAME_PAGE_CODE);
30
+ const [Page, loading, error] = useAsyncData(GAME_PAGE_CODE, {
31
+ loading: true,
32
+ error: true,
33
+ });
34
+ if (loading) {
35
+ return <GameScreenPending />;
36
+ }
37
+ if (error) {
38
+ return <GameScreenUnavailable />;
39
+ }
29
40
  return <Page />;
30
41
  };
31
42
 
32
- <Route>
33
- <ErrorBoundary fallback={PageError}>
34
- <Loading fallback={<PageSkeleton />}>
35
- <Route route={GAME_ROUTE} element={GamePage} />
36
- </Loading>
37
- </ErrorBoundary>
38
- </Route>;
43
+ <Route route={GAME_ROUTE} element={GamePage} />;
39
44
  ```
40
45
 
41
- Everything the data layer decides then holds for the code, with nothing more
42
- to write:
43
-
44
- - **One wait.** The page suspends into the nearest `<Loading>` on whichever of
45
- the two arrives last; the boundary is told it is loading and draws its
46
- fallback; the routing is busy until both are there.
47
- - **One failure.** A rejected import fails the run: the nearest
48
- `<ErrorBoundary>` shows it, `error.action.rerun()` is the way back, going
49
- somewhere else leaves it behind.
46
+ Why a branch rather than a `<Loading>` around it: suspending takes the
47
+ subtree away and puts the boundary's fallback in its place, then mounts the
48
+ page again — a cost worth paying when a page already on screen re-reads its
49
+ data (see [data_states.md](./data_states.md)), and worth nothing here, where
50
+ the page is not there yet and has nothing to keep. `loading: true` never
51
+ suspends: one component, no wrapper, the pending screen is the page's own
52
+ shape, and the failure sits beside it (what it offers: below, "When the code
53
+ does not come"). What is delegated stays delegated — the page's data, read inside
54
+ `game_page.jsx` with `useAsyncData(GAME_PAGE_ACTION)`, still waits in the
55
+ `<Loading>` written between the routes, as [navigation.md](./navigation.md)
56
+ says.
57
+
58
+ Everything the data layer decides then holds for the code:
59
+
60
+ - **One failure.** A rejected import fails the run like a failed read; drawn
61
+ by the branch above, or by the nearest `<ErrorBoundary>` when the page reads
62
+ its code without `error: true`.
50
63
  - **Fetched once.** A route action left behind is aborted, not reset, and a
51
64
  completed one is not run again: coming back to the page renders
52
65
  synchronously, only the data is asked for per params.
53
66
  - **Fetched ahead.** A route action that asks nothing of the address is prerun
54
67
  on intent (below).
68
+ - **Counted.** The routing is busy until the code is there, like for the
69
+ data.
55
70
 
56
71
  The page module itself is ordinary — it reads its data with
57
72
  `useAsyncData(GAME_PAGE_ACTION)` like any page — and imports the routes module
@@ -75,19 +90,19 @@ const PlanSection = () => {
75
90
  return (
76
91
  <PlanFrame>
77
92
  {loading ? <Text>…</Text> : null}
78
- {error ? (
79
- <Button action={() => error.action.rerun()}>Réessayer</Button>
80
- ) : null}
93
+ {error ? <Button action={() => reload()}>Recharger</Button> : null}
81
94
  {Plan ? <Plan /> : null}
82
95
  </PlanFrame>
83
96
  );
84
97
  };
85
98
  ```
86
99
 
87
- Delegating instead (`useAsyncData(fn)` alone, under a `<Loading>` around the
88
- frame) is the same hook with the same rule as for data: **the `<Loading>` goes
89
- where the fallback should land.** A boundary around the whole router takes the
90
- router away while a page loads — top bar, tabs and all.
100
+ The same reasons as for a page hold: nothing stands there yet, so a
101
+ suspension would only swap and remount for nothing. Delegating instead
102
+ (`useAsyncData(fn)` alone, under a `<Loading>` around the frame) follows the
103
+ rule for data: **the `<Loading>` goes where the fallback should land.** A
104
+ boundary around the whole router takes the router away while a page loads —
105
+ top bar, tabs and all.
91
106
 
92
107
  This shape starts at the render and is per instance: a component fetched this
93
108
  way in three places asks three times (the browser fetches the module once),
@@ -111,8 +126,9 @@ prefetched: a prefetch has no address, the id may still be chosen.
111
126
  `routeAction(route, action, undefined, { prefetch: false })` keeps a
112
127
  param-less read that is not worth a hover out of it.
113
128
 
114
- A prefetch that fails is forgotten, not reported: nothing on screen asked for
115
- it. The arrival asks again and shows its own failure.
129
+ A prefetch that fails is not reported: nothing on screen asked for it. The
130
+ failure stays on the action, where the arrival asks again — a failed action is
131
+ run, only a running or completed one is left alone — and shows its own.
116
132
 
117
133
  The same from code: `route.preload()` for a route something is about to
118
134
  navigate to, `preloadUrl(url)` for an address. A press that computes its
@@ -120,9 +136,10 @@ destination has nothing to preload — the navigation itself asks.
120
136
 
121
137
  ## What a transition photographs
122
138
 
123
- A route element still waiting is announced as rendered by its boundary, so a
124
- route transition or a `RouteTravel` takes its picture of the **fallback**, code
125
- and data alike. Navi does not hold a navigation for a chunk: the honest answer
139
+ A route element still waiting is what the container rendered — its pending
140
+ screen, or the fallback of the boundary it suspended into — so a route
141
+ transition or a `RouteTravel` takes its picture of **that**, code and data
142
+ alike. Navi does not hold a navigation for a chunk: the honest answer
126
143
  to "a chunk is a hundred milliseconds" is the prefetch above — the code is
127
144
  there before the press — and a skeleton that is the page's own shape is what
128
145
  gets photographed when it is not.
@@ -154,15 +171,22 @@ is a 404; or the network is gone. The browser says both the same way — a
154
171
  `TypeError` with no status — and neither is a bug of the page: there is
155
172
  nothing in the app's code to point at.
156
173
 
157
- The component reading its own code says it where it stands, as above. For a
158
- page, the failure reaches the boundary like a failed read does; a boundary
159
- that shows only what came from the network and rethrows the rest as a bug
160
- recognises an import by its action — `error.action` is the route action the
161
- app declared for the code — rather than by the browser's message. Either way
162
- `error.action.rerun()` retries; a retry failing the same way says the document
163
- is stale, and **reloading it** is what brings the new code. Navi does not do
164
- it on its own — a document reloading itself is a loop waiting to happen — the
165
- screen says it.
174
+ **Asking again never fetches.** The document remembers a module whose fetch
175
+ failed as failed: a second `import()` of the same address is refused without a
176
+ request, for the life of the document. So a retry on an import has nothing to
177
+ do — `error.action.rerun()` fails again at once, on the same failure — and
178
+ the one way out is a fresh document: **`reload()`**, which the screen offers
179
+ in words the reader understands. Navi does not reload on its own: a document
180
+ reloading itself is a loop waiting to happen. It is also why a prefetch that
181
+ failed is not asked again on the arrival in any useful sense: the run is made,
182
+ refused at once, and the screen says it.
183
+
184
+ The page or the component reading its code says it where it stands, as
185
+ above. A failure left to a boundary — a page reading its code without
186
+ `error: true` — reaches it like a failed read does; a boundary that shows only
187
+ what came from the network and rethrows the rest as a bug recognises an import
188
+ by its action — `error.action` is the route action the app declared for the
189
+ code — rather than by the browser's message.
166
190
 
167
191
  ## Preact's `lazy()` is not this
168
192
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.225",
3
+ "version": "0.29.226",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {