@rangojs/router 0.28.0 → 0.30.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/dist/bin/rango.js +1 -1
- package/dist/types/browser/action-fence.d.ts +2 -1
- package/dist/types/browser/event-controller.d.ts +6 -1
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/use-action.d.ts +10 -4
- package/dist/types/browser/react/use-handle.d.ts +20 -16
- package/dist/types/browser/react/use-link-status.d.ts +5 -2
- package/dist/types/browser/react/use-route-state.d.ts +45 -0
- package/dist/types/browser/segment-reconciler.d.ts +26 -0
- package/dist/types/browser/types.d.ts +30 -8
- package/dist/types/browser/wire-protocol.d.ts +1 -0
- package/dist/types/cache/tag-invalidation.d.ts +0 -10
- package/dist/types/router/error-handling.d.ts +7 -1
- package/dist/types/router/match-context.d.ts +2 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +3 -2
- package/dist/types/router/navigation-snapshot.d.ts +7 -0
- package/dist/types/router/segment-resolution/helpers.d.ts +19 -3
- package/dist/types/router/segment-resolution/resolution-pass.d.ts +2 -0
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +2 -0
- package/dist/types/router/types.d.ts +1 -1
- package/dist/types/server/request-context.d.ts +30 -1
- package/dist/types/testing/render-route.d.ts +43 -8
- package/dist/types/testing/serve-shell-request.d.ts +8 -1
- package/dist/types/types/error-types.d.ts +27 -8
- package/dist/types/wire-params.d.ts +5 -0
- package/dist/vite/index.js +62 -67
- package/package.json +1 -1
- package/skills/client-urls/SKILL.md +6 -8
- package/skills/hooks/SKILL.md +1 -1
- package/skills/hooks/data.md +8 -1
- package/skills/hooks/handle-and-actions.md +40 -2
- package/skills/hooks/navigation.md +121 -15
- package/skills/hooks/state.md +6 -4
- package/skills/hooks/urls.md +17 -5
- package/skills/intercept/SKILL.md +6 -0
- package/skills/observability/SKILL.md +30 -0
- package/skills/server-actions/SKILL.md +8 -1
- package/skills/testing/client-components.md +25 -18
- package/skills/testing/serve-shell-request.md +7 -7
- package/skills/view-transitions/SKILL.md +1 -1
- package/src/browser/action-fence.ts +2 -1
- package/src/browser/event-controller.ts +78 -19
- package/src/browser/navigation-bridge.ts +45 -13
- package/src/browser/navigation-client.ts +2 -0
- package/src/browser/network-error-handler.ts +4 -0
- package/src/browser/partial-update.ts +33 -6
- package/src/browser/react/use-action.ts +27 -50
- package/src/browser/react/use-handle.ts +27 -18
- package/src/browser/react/use-link-status.ts +48 -60
- package/src/browser/react/use-navigation.ts +76 -51
- package/src/browser/react/use-params.ts +6 -35
- package/src/browser/react/use-pathname.ts +3 -32
- package/src/browser/react/use-route-state.ts +141 -0
- package/src/browser/react/use-search-params.ts +7 -40
- package/src/browser/segment-reconciler.ts +43 -0
- package/src/browser/server-action-bridge.ts +35 -8
- package/src/browser/types.ts +31 -8
- package/src/browser/wire-protocol.ts +2 -0
- package/src/cache/cache-runtime.ts +28 -31
- package/src/cache/document-cache.ts +34 -37
- package/src/cache/read-through-swr.ts +20 -18
- package/src/cache/tag-invalidation.ts +3 -18
- package/src/router/error-handling.ts +18 -6
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/match-api.ts +1 -0
- package/src/router/match-context.ts +3 -0
- package/src/router/match-middleware/background-revalidation.ts +5 -4
- package/src/router/match-middleware/cache-lookup.ts +5 -3
- package/src/router/navigation-snapshot.ts +10 -0
- package/src/router/segment-resolution/helpers.ts +49 -13
- package/src/router/segment-resolution/loader-cache.ts +4 -3
- package/src/router/segment-resolution/resolution-pass.ts +2 -0
- package/src/router/segment-resolution/revalidation.ts +57 -28
- package/src/router/segment-resolution/streamed-handler-boundary.ts +3 -0
- package/src/router/types.ts +1 -1
- package/src/router.ts +7 -4
- package/src/rsc/request-stages.ts +4 -2
- package/src/rsc/shell-capture.ts +63 -58
- package/src/server/request-context.ts +49 -0
- package/src/testing/dispatch.ts +3 -0
- package/src/testing/render-route.tsx +204 -16
- package/src/testing/serve-shell-request.ts +19 -10
- package/src/types/error-types.ts +27 -8
- package/src/wire-params.ts +5 -0
|
@@ -26,24 +26,30 @@
|
|
|
26
26
|
* - navigate() drives the navigation lifecycle as a click does:
|
|
27
27
|
* useNavigation() reads loading from the call and idle with the commit,
|
|
28
28
|
* and useLinkStatus().pending is true for a link to the destination until
|
|
29
|
-
* the commit
|
|
29
|
+
* the commit, which React holds with it. A `transition` spec plus a pending
|
|
30
30
|
* Promise seeded via navigate(url, { loaders }) commits through
|
|
31
31
|
* production's commitInTransition (browser/partial-update.ts), which React
|
|
32
32
|
* holds: useNavigation() stays loading and the held reader's
|
|
33
33
|
* useLoader().isLoading reads true until the data lands. Without
|
|
34
34
|
* `transition` the commit is urgent (production's same-structure hold is
|
|
35
|
-
* not modeled).
|
|
35
|
+
* not modeled). navigate(url, { streamUntil }) gives the navigation a
|
|
36
|
+
* response stream that stays open past the commit (isStreaming).
|
|
37
|
+
* useAction().state stays idle: no action runs.
|
|
36
38
|
* - A spec's transition({ when }) decides every commit with production's
|
|
37
39
|
* browser decision: navigate() and useRouter().push model kind "push",
|
|
38
|
-
* useRouter().replace kind "replace", refresh() kind "revalidate"
|
|
39
|
-
*
|
|
40
|
-
* e2e territory.
|
|
40
|
+
* useRouter().replace kind "replace", refresh() kind "revalidate", and a
|
|
41
|
+
* back/forward that restores a page kind "pop". Action and optimistic
|
|
42
|
+
* clientUrls() swap decisions are e2e territory.
|
|
41
43
|
* - Location state reaches readers the way it does in production:
|
|
42
44
|
* NavigationProvider holds the committed entry's state next to the
|
|
43
45
|
* payload, so a navigation whose loader the test holds keeps the entry
|
|
44
46
|
* being left on screen with its own state. A `popstate` event is a
|
|
45
|
-
* back/forward onto the entry history.state holds
|
|
46
|
-
*
|
|
47
|
+
* back/forward onto the entry history.state holds. When it is the
|
|
48
|
+
* mounted entry or one a navigate() wrote, history still holds the state
|
|
49
|
+
* written there, and another page is on screen, its page is restored
|
|
50
|
+
* from the history cache, with the handle values it had, as production's
|
|
51
|
+
* restore is; otherwise the tree keeps its location. A stale entry's
|
|
52
|
+
* revalidation, a restore that fetches, a server action's state, a
|
|
47
53
|
* redirect's and an intercept's are e2e territory.
|
|
48
54
|
* What it DOES cover: client hooks that read NavigationProvider /
|
|
49
55
|
* OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
|
|
@@ -361,8 +367,37 @@ export interface TestRouterHandle {
|
|
|
361
367
|
* `handles`). They arrive on the response's handle stream, so `useHandle`
|
|
362
368
|
* readers take them with the page's commit: while React holds the page,
|
|
363
369
|
* readers keep the values of the page being left.
|
|
370
|
+
*
|
|
371
|
+
* `options.streamUntil` gives the navigation a response stream, as a real
|
|
372
|
+
* response has: it opens in a task after the call, before the commit (the
|
|
373
|
+
* response arrives, and `useNavigation().pendingUrl` clears), and stays open
|
|
374
|
+
* until the promise settles (a page that streams a tail past its commit:
|
|
375
|
+
* `isStreaming` reads true until then). Without it the navigation has no
|
|
376
|
+
* stream. A navigation that a newer one or a back/forward aborts before its
|
|
377
|
+
* response arrives opens none and commits nothing; one aborted after its
|
|
378
|
+
* response arrived stops counting as streaming at the abort, as in
|
|
379
|
+
* production, where its response streams on and is ignored.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* const tail = deferred();
|
|
383
|
+
* await router.navigate("/products/2", { streamUntil: tail.promise });
|
|
384
|
+
* // useNavigation(): idle, isStreaming true
|
|
385
|
+
* await act(async () => tail.resolve());
|
|
386
|
+
* // useNavigation(): idle, isStreaming false
|
|
387
|
+
*
|
|
388
|
+
* A navigation started while React holds a page shows its page once the
|
|
389
|
+
* call resolves, with or without act(). A `<Link>` click over a held page
|
|
390
|
+
* still goes inside `await act(async () => ...)` (docs/testing.md).
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* await router.navigate("/products/2", { loaders: [[ProductLoader, next]] });
|
|
394
|
+
* // React holds /products/2
|
|
395
|
+
* await router.navigate("/products/3", { transition: false });
|
|
396
|
+
* // /products/3 is on screen
|
|
364
397
|
*/
|
|
365
|
-
navigate(url: string, options?: Pick<RenderRouteOptions, "loaders" | "handles"> & Pick<NavigateOptions, "state" | "replace" | "transition">
|
|
398
|
+
navigate(url: string, options?: Pick<RenderRouteOptions, "loaders" | "handles"> & Pick<NavigateOptions, "state" | "replace" | "transition"> & {
|
|
399
|
+
streamUntil?: Promise<unknown>;
|
|
400
|
+
}): Promise<void>;
|
|
366
401
|
/**
|
|
367
402
|
* Re-render the current location, as router.refresh() does: a spec's
|
|
368
403
|
* transition({ when }) decides it with kind "revalidate" (`to` is `from`).
|
|
@@ -81,7 +81,13 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
81
81
|
* and segments take an action's revalidation defaults; it renders live, as
|
|
82
82
|
* an action does, so neither the route cache nor the document cache answers
|
|
83
83
|
* or stores it, and a `ppr` route's `replayStatus` is
|
|
84
|
-
* `{ outcome: "BYPASS", reason: "stale" }`.
|
|
84
|
+
* `{ outcome: "BYPASS", reason: "stale" }`. `boundary` names the error
|
|
85
|
+
* segment of a thrown action's boundary the page at `from` shows
|
|
86
|
+
* (`_rsc_boundary`), as the follow-up an earlier action owes that page
|
|
87
|
+
* sends it, with `stale: true` and no `segments`: the response renders
|
|
88
|
+
* every segment above the boundary and nothing at or below it, claims the
|
|
89
|
+
* boundary in `matched` and sends no segment for it. The server reads it
|
|
90
|
+
* only beside `stale`.
|
|
85
91
|
*/
|
|
86
92
|
partial?: true | {
|
|
87
93
|
from?: string;
|
|
@@ -91,6 +97,7 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
91
97
|
fill?: boolean;
|
|
92
98
|
interceptSource?: string;
|
|
93
99
|
stale?: boolean;
|
|
100
|
+
boundary?: string;
|
|
94
101
|
};
|
|
95
102
|
/**
|
|
96
103
|
* POST this form as a submit before hydration: the no-JS request the
|
|
@@ -67,19 +67,38 @@ export interface OnErrorContext<TEnv = any> {
|
|
|
67
67
|
actionId?: string;
|
|
68
68
|
/** Environment/bindings (platform context) */
|
|
69
69
|
env?: TEnv;
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Duration from request start to error (milliseconds). For a `background`
|
|
72
|
+
* error it counts from the start of the request that started the work, so
|
|
73
|
+
* it includes that response and any wait before the work ran.
|
|
74
|
+
*/
|
|
71
75
|
duration?: number;
|
|
72
76
|
/**
|
|
73
77
|
* Whether this is a partial/navigation request, on an error the segment
|
|
74
|
-
* walk catches (a handler that throws, a
|
|
75
|
-
* for a navigation, unset for a
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* either render; the `routing` phase reports `false`.
|
|
78
|
+
* walk catches (a handler that throws, a streamed `loading()` handler that
|
|
79
|
+
* rejects, a DSL loader that rejects): `true` for a navigation, unset for a
|
|
80
|
+
* document. A `cache()` record's refresh or proactive write reports the
|
|
81
|
+
* request that started it; a PPR shell capture renders a document. The
|
|
82
|
+
* `routing` phase reports `false`.
|
|
80
83
|
*/
|
|
81
84
|
isPartial?: boolean;
|
|
82
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* `true` when the error came from work whose output goes to a cache and
|
|
87
|
+
* never to the response of the request that started it: a stale `cache()`
|
|
88
|
+
* record's refresh, the write of a navigation that kept a layout mounted, a
|
|
89
|
+
* stale document cache entry's re-run, a PPR shell capture, or a stale
|
|
90
|
+
* `"use cache"` function's or cached loader's refresh, the work's own
|
|
91
|
+
* failure included. `request` and `url` are the starting request's (a
|
|
92
|
+
* shell capture's handler errors name the page it captures). A
|
|
93
|
+
* `router.prerender()` warm's own render is its request's: unset, while a
|
|
94
|
+
* shell capture it makes is `true`. Unset for every other error.
|
|
95
|
+
*/
|
|
96
|
+
background?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Whether an error boundary caught the error and its fallback reached the
|
|
99
|
+
* response. `false` on a `background` error: the failed render is never
|
|
100
|
+
* stored, so no visitor sees the fallback.
|
|
101
|
+
*/
|
|
83
102
|
handledByBoundary?: boolean;
|
|
84
103
|
/** Stack trace (if available) */
|
|
85
104
|
stack?: string;
|
|
@@ -19,6 +19,11 @@ export declare const WIRE: {
|
|
|
19
19
|
readonly rewriteTarget: "_rsc_rt";
|
|
20
20
|
/** A stale request: `true` (router/navigation-snapshot.ts). */
|
|
21
21
|
readonly stale: "_rsc_stale";
|
|
22
|
+
/**
|
|
23
|
+
* The id of an action's error boundary on screen that a stale request
|
|
24
|
+
* holds: read only beside `stale` (router/navigation-snapshot.ts).
|
|
25
|
+
*/
|
|
26
|
+
readonly boundary: "_rsc_boundary";
|
|
22
27
|
/** A fill request (docs/design/prefetch-false.md): `1`. */
|
|
23
28
|
readonly fill: "_rsc_fill";
|
|
24
29
|
/** A loader fetch's loader id (rsc/loader-fetch.ts). */
|
package/dist/vite/index.js
CHANGED
|
@@ -298,6 +298,11 @@ var init_wire_params = __esm({
|
|
|
298
298
|
rewriteTarget: "_rsc_rt",
|
|
299
299
|
/** A stale request: `true` (router/navigation-snapshot.ts). */
|
|
300
300
|
stale: "_rsc_stale",
|
|
301
|
+
/**
|
|
302
|
+
* The id of an action's error boundary on screen that a stale request
|
|
303
|
+
* holds: read only beside `stale` (router/navigation-snapshot.ts).
|
|
304
|
+
*/
|
|
305
|
+
boundary: "_rsc_boundary",
|
|
301
306
|
/** A fill request (docs/design/prefetch-false.md): `1`. */
|
|
302
307
|
fill: "_rsc_fill",
|
|
303
308
|
/** A loader fetch's loader id (rsc/loader-fetch.ts). */
|
|
@@ -723,6 +728,7 @@ function writePartialParams(url, init) {
|
|
|
723
728
|
params.set(WIRE.segments, init.segmentIds.join(","));
|
|
724
729
|
}
|
|
725
730
|
if (init.stale) params.set(WIRE.stale, "true");
|
|
731
|
+
if (init.boundary) params.set(WIRE.boundary, init.boundary);
|
|
726
732
|
if (init.version) params.set(WIRE.version, init.version);
|
|
727
733
|
if (init.routerId) params.set(WIRE.routerId, init.routerId);
|
|
728
734
|
if (init.rewriteTarget) params.set(WIRE.rewriteTarget, init.rewriteTarget);
|
|
@@ -4485,13 +4491,13 @@ import {
|
|
|
4485
4491
|
Component as Component3,
|
|
4486
4492
|
createElement as createElement4,
|
|
4487
4493
|
isValidElement as isValidElement2,
|
|
4488
|
-
useContext as
|
|
4494
|
+
useContext as useContext18,
|
|
4489
4495
|
useMemo as useMemo9,
|
|
4490
4496
|
Suspense as Suspense2
|
|
4491
4497
|
} from "react";
|
|
4492
4498
|
|
|
4493
4499
|
// src/browser/react/use-handle.ts
|
|
4494
|
-
import { useContext, useState, useEffect, useRef } from "react";
|
|
4500
|
+
import { useContext as useContext2, useState as useState2, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
4495
4501
|
|
|
4496
4502
|
// src/missing-id-error.ts
|
|
4497
4503
|
var SELF_FILES = /* @__PURE__ */ new Set([
|
|
@@ -4569,6 +4575,19 @@ var NavigationStoreContext = createContext(null);
|
|
|
4569
4575
|
var LocationStateContext = createContext(void 0);
|
|
4570
4576
|
var HydrationBarrierContext = createContext(0);
|
|
4571
4577
|
|
|
4578
|
+
// src/browser/react/use-route-state.ts
|
|
4579
|
+
import {
|
|
4580
|
+
useContext,
|
|
4581
|
+
useEffect,
|
|
4582
|
+
useLayoutEffect,
|
|
4583
|
+
useRef,
|
|
4584
|
+
useState
|
|
4585
|
+
} from "react";
|
|
4586
|
+
var NO_ROUTE = {
|
|
4587
|
+
location: new URL("/", "http://localhost"),
|
|
4588
|
+
params: Object.freeze({})
|
|
4589
|
+
};
|
|
4590
|
+
|
|
4572
4591
|
// src/handles/meta.ts
|
|
4573
4592
|
function isUnsetDescriptor(descriptor) {
|
|
4574
4593
|
return typeof descriptor === "object" && descriptor !== null && "unset" in descriptor && typeof descriptor.unset === "string";
|
|
@@ -4698,18 +4717,18 @@ function collectMeta(segments) {
|
|
|
4698
4717
|
var Meta = createHandle(collectMeta, "__rsc_router_meta__");
|
|
4699
4718
|
|
|
4700
4719
|
// src/theme/theme-context.ts
|
|
4701
|
-
import { createContext as createContext2, useContext as
|
|
4720
|
+
import { createContext as createContext2, useContext as useContext3 } from "react";
|
|
4702
4721
|
var ThemeContext = createContext2(null);
|
|
4703
4722
|
|
|
4704
4723
|
// src/browser/react/nonce-context.ts
|
|
4705
|
-
import { createContext as createContext3, useContext as
|
|
4724
|
+
import { createContext as createContext3, useContext as useContext4 } from "react";
|
|
4706
4725
|
var NonceContext = createContext3(void 0);
|
|
4707
4726
|
|
|
4708
4727
|
// src/handles/MetaTags.tsx
|
|
4709
4728
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4710
4729
|
|
|
4711
4730
|
// src/handles/Scripts.tsx
|
|
4712
|
-
import { useState as
|
|
4731
|
+
import { useState as useState3 } from "react";
|
|
4713
4732
|
|
|
4714
4733
|
// src/handles/script.ts
|
|
4715
4734
|
function validateConfigDev(config) {
|
|
@@ -4763,7 +4782,7 @@ var Script = createHandle(collectScripts, "__rsc_router_script__");
|
|
|
4763
4782
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
4764
4783
|
|
|
4765
4784
|
// src/browser/react/ScrollRestoration.tsx
|
|
4766
|
-
import { useEffect as
|
|
4785
|
+
import { useEffect as useEffect3 } from "react";
|
|
4767
4786
|
|
|
4768
4787
|
// src/browser/scroll-restoration.ts
|
|
4769
4788
|
init_logging();
|
|
@@ -4777,12 +4796,12 @@ import { Component, Suspense, use as use2, useMemo as useMemo3 } from "react";
|
|
|
4777
4796
|
|
|
4778
4797
|
// src/outlet-provider.tsx
|
|
4779
4798
|
import {
|
|
4780
|
-
useContext as
|
|
4799
|
+
useContext as useContext5,
|
|
4781
4800
|
useMemo
|
|
4782
4801
|
} from "react";
|
|
4783
4802
|
|
|
4784
4803
|
// src/suspense-audit-react.tsx
|
|
4785
|
-
import { useEffect as
|
|
4804
|
+
import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
|
|
4786
4805
|
|
|
4787
4806
|
// src/suspense-audit.ts
|
|
4788
4807
|
init_internal_debug();
|
|
@@ -4797,13 +4816,13 @@ import { jsx as jsx3 } from "react/jsx-runtime";
|
|
|
4797
4816
|
import { createElement as createElement2 } from "react";
|
|
4798
4817
|
|
|
4799
4818
|
// src/loader-redirect.tsx
|
|
4800
|
-
import { useEffect as
|
|
4819
|
+
import { useEffect as useEffect5 } from "react";
|
|
4801
4820
|
|
|
4802
4821
|
// src/browser/react/use-router.ts
|
|
4803
|
-
import { useContext as
|
|
4822
|
+
import { useContext as useContext7, useMemo as useMemo2 } from "react";
|
|
4804
4823
|
|
|
4805
4824
|
// src/browser/react/use-mount.ts
|
|
4806
|
-
import { useContext as
|
|
4825
|
+
import { useContext as useContext6 } from "react";
|
|
4807
4826
|
|
|
4808
4827
|
// src/browser/react/mount-context.ts
|
|
4809
4828
|
import {
|
|
@@ -4996,12 +5015,12 @@ import {
|
|
|
4996
5015
|
startTransition,
|
|
4997
5016
|
use as use3,
|
|
4998
5017
|
useCallback,
|
|
4999
|
-
useContext as
|
|
5000
|
-
useEffect as
|
|
5018
|
+
useContext as useContext8,
|
|
5019
|
+
useEffect as useEffect6,
|
|
5001
5020
|
useMemo as useMemo4,
|
|
5002
5021
|
useOptimistic,
|
|
5003
|
-
useRef as
|
|
5004
|
-
useState as
|
|
5022
|
+
useRef as useRef4,
|
|
5023
|
+
useState as useState4
|
|
5005
5024
|
} from "react";
|
|
5006
5025
|
|
|
5007
5026
|
// src/loader-store.ts
|
|
@@ -5163,45 +5182,23 @@ function insertSegments(node, segments, index, leafBase, paramNames) {
|
|
|
5163
5182
|
}
|
|
5164
5183
|
|
|
5165
5184
|
// src/browser/react/use-navigation.ts
|
|
5166
|
-
import {
|
|
5167
|
-
useContext as useContext8,
|
|
5168
|
-
useState as useState4,
|
|
5169
|
-
useEffect as useEffect6,
|
|
5170
|
-
useLayoutEffect as useLayoutEffect2,
|
|
5171
|
-
startTransition as startTransition2,
|
|
5172
|
-
useRef as useRef4
|
|
5173
|
-
} from "react";
|
|
5185
|
+
import { useContext as useContext9, useEffect as useEffect7, useRef as useRef5 } from "react";
|
|
5174
5186
|
|
|
5175
5187
|
// src/browser/react/use-pathname.ts
|
|
5176
|
-
import { useContext as
|
|
5188
|
+
import { useContext as useContext10 } from "react";
|
|
5177
5189
|
|
|
5178
5190
|
// src/client-urls/optimistic-location.ts
|
|
5179
5191
|
import { createContext as createContext6 } from "react";
|
|
5180
5192
|
var OptimisticLocationContext = createContext6(null);
|
|
5181
5193
|
|
|
5182
5194
|
// src/browser/react/use-search-params.ts
|
|
5183
|
-
import {
|
|
5184
|
-
useCallback as useCallback2,
|
|
5185
|
-
useContext as useContext10,
|
|
5186
|
-
useEffect as useEffect8,
|
|
5187
|
-
useMemo as useMemo5,
|
|
5188
|
-
useRef as useRef6,
|
|
5189
|
-
useState as useState6
|
|
5190
|
-
} from "react";
|
|
5195
|
+
import { useCallback as useCallback2, useContext as useContext11, useMemo as useMemo5 } from "react";
|
|
5191
5196
|
|
|
5192
5197
|
// src/browser/react/use-params.ts
|
|
5193
|
-
import { useContext as
|
|
5194
|
-
var EMPTY_PARAMS = Object.freeze({});
|
|
5198
|
+
import { useContext as useContext12 } from "react";
|
|
5195
5199
|
|
|
5196
5200
|
// src/browser/react/use-action.ts
|
|
5197
|
-
import {
|
|
5198
|
-
useContext as useContext12,
|
|
5199
|
-
useState as useState8,
|
|
5200
|
-
useEffect as useEffect10,
|
|
5201
|
-
useRef as useRef8,
|
|
5202
|
-
useOptimistic as useOptimistic2,
|
|
5203
|
-
startTransition as startTransition3
|
|
5204
|
-
} from "react";
|
|
5201
|
+
import { useContext as useContext13, useState as useState5, useEffect as useEffect8, useRef as useRef6 } from "react";
|
|
5205
5202
|
|
|
5206
5203
|
// src/browser/event-controller.ts
|
|
5207
5204
|
init_notify_listeners();
|
|
@@ -5264,31 +5261,31 @@ function subscribeToLocationChange(eventController, listener) {
|
|
|
5264
5261
|
}
|
|
5265
5262
|
|
|
5266
5263
|
// src/browser/react/use-segments.ts
|
|
5267
|
-
import { useContext as
|
|
5264
|
+
import { useContext as useContext14, useEffect as useEffect9, useRef as useRef7 } from "react";
|
|
5268
5265
|
|
|
5269
5266
|
// src/browser/react/NavigationProvider.tsx
|
|
5270
5267
|
import {
|
|
5271
|
-
useState as
|
|
5272
|
-
useEffect as
|
|
5268
|
+
useState as useState8,
|
|
5269
|
+
useEffect as useEffect11,
|
|
5273
5270
|
useLayoutEffect as useLayoutEffect4,
|
|
5274
5271
|
useCallback as useCallback4,
|
|
5275
5272
|
useMemo as useMemo7,
|
|
5276
|
-
useRef as
|
|
5273
|
+
useRef as useRef9,
|
|
5277
5274
|
use as use4,
|
|
5278
|
-
startTransition as
|
|
5275
|
+
startTransition as startTransition2
|
|
5279
5276
|
} from "react";
|
|
5280
5277
|
|
|
5281
5278
|
// src/root-error-boundary.tsx
|
|
5282
|
-
import { Component as Component2, useState as
|
|
5279
|
+
import { Component as Component2, useState as useState6 } from "react";
|
|
5283
5280
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
5284
5281
|
|
|
5285
5282
|
// src/theme/ThemeProvider.tsx
|
|
5286
5283
|
import {
|
|
5287
5284
|
useCallback as useCallback3,
|
|
5288
|
-
useEffect as
|
|
5285
|
+
useEffect as useEffect10,
|
|
5289
5286
|
useLayoutEffect as useLayoutEffect3,
|
|
5290
5287
|
useMemo as useMemo6,
|
|
5291
|
-
useState as
|
|
5288
|
+
useState as useState7
|
|
5292
5289
|
} from "react";
|
|
5293
5290
|
|
|
5294
5291
|
// src/theme/constants.ts
|
|
@@ -5322,21 +5319,18 @@ import { jsx as jsx7 } from "react/jsx-runtime";
|
|
|
5322
5319
|
import {
|
|
5323
5320
|
forwardRef,
|
|
5324
5321
|
useCallback as useCallback5,
|
|
5325
|
-
useContext as
|
|
5326
|
-
useEffect as
|
|
5322
|
+
useContext as useContext16,
|
|
5323
|
+
useEffect as useEffect13,
|
|
5327
5324
|
useMemo as useMemo8,
|
|
5328
|
-
useRef as
|
|
5325
|
+
useRef as useRef10
|
|
5329
5326
|
} from "react";
|
|
5330
5327
|
|
|
5331
5328
|
// src/browser/react/use-link-status.ts
|
|
5332
5329
|
import {
|
|
5333
5330
|
createContext as createContext7,
|
|
5334
|
-
useContext as
|
|
5335
|
-
useState as
|
|
5336
|
-
useEffect as
|
|
5337
|
-
useRef as useRef12,
|
|
5338
|
-
useOptimistic as useOptimistic3,
|
|
5339
|
-
startTransition as startTransition5
|
|
5331
|
+
useContext as useContext15,
|
|
5332
|
+
useState as useState9,
|
|
5333
|
+
useEffect as useEffect12
|
|
5340
5334
|
} from "react";
|
|
5341
5335
|
var LinkContext = createContext7(
|
|
5342
5336
|
null
|
|
@@ -5537,14 +5531,14 @@ var Link = forwardRef(function Link2({
|
|
|
5537
5531
|
onClick,
|
|
5538
5532
|
...props
|
|
5539
5533
|
}, ref) {
|
|
5540
|
-
const ctx =
|
|
5534
|
+
const ctx = useContext16(NavigationStoreContext);
|
|
5541
5535
|
const isExternal = isExternalUrl(to, ctx?.origin);
|
|
5542
5536
|
const resolvedTo = useMemo8(
|
|
5543
5537
|
() => isExternal ? to : prefixBasename(to, ctx?.basename),
|
|
5544
5538
|
[to, isExternal, ctx?.basename]
|
|
5545
5539
|
);
|
|
5546
5540
|
const configuredStrategy = prefetch ?? ctx?.defaultPrefetch ?? ENVIRONMENT_DEFAULT_PREFETCH;
|
|
5547
|
-
const internalRef =
|
|
5541
|
+
const internalRef = useRef10(null);
|
|
5548
5542
|
const setRef = useCallback5(
|
|
5549
5543
|
(node) => {
|
|
5550
5544
|
internalRef.current = node;
|
|
@@ -5556,7 +5550,7 @@ var Link = forwardRef(function Link2({
|
|
|
5556
5550
|
},
|
|
5557
5551
|
[ref]
|
|
5558
5552
|
);
|
|
5559
|
-
const stateRef =
|
|
5553
|
+
const stateRef = useRef10(state);
|
|
5560
5554
|
stateRef.current = state;
|
|
5561
5555
|
const handleClick = useCallback5(
|
|
5562
5556
|
(e) => {
|
|
@@ -5613,7 +5607,7 @@ var Link = forwardRef(function Link2({
|
|
|
5613
5607
|
);
|
|
5614
5608
|
}
|
|
5615
5609
|
}, [configuredStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
|
|
5616
|
-
|
|
5610
|
+
useEffect13(() => {
|
|
5617
5611
|
if (isExternal || !ctx?.store) return;
|
|
5618
5612
|
if (configuredStrategy !== "viewport" && configuredStrategy !== "render" && configuredStrategy !== "adaptive") {
|
|
5619
5613
|
return;
|
|
@@ -5733,7 +5727,7 @@ var Breadcrumbs = createHandle(
|
|
|
5733
5727
|
);
|
|
5734
5728
|
|
|
5735
5729
|
// src/browser/react/location-state.ts
|
|
5736
|
-
import { useContext as
|
|
5730
|
+
import { useContext as useContext17, useEffect as useEffect14 } from "react";
|
|
5737
5731
|
|
|
5738
5732
|
// src/browser/react/use-href.tsx
|
|
5739
5733
|
import { useCallback as useCallback6 } from "react";
|
|
@@ -5936,6 +5930,7 @@ var DERIVED_RENDER_BARRIER_DESCRIPTOR = {
|
|
|
5936
5930
|
...RENDER_BARRIER_DESCRIPTOR,
|
|
5937
5931
|
enumerable: false
|
|
5938
5932
|
};
|
|
5933
|
+
var backgroundWork = new AsyncLocalStorage6();
|
|
5939
5934
|
|
|
5940
5935
|
// src/route-definition/redirect.ts
|
|
5941
5936
|
init_redirect_origin();
|
|
@@ -5948,11 +5943,11 @@ import {
|
|
|
5948
5943
|
useDeferredValue,
|
|
5949
5944
|
useLayoutEffect as useLayoutEffect5,
|
|
5950
5945
|
useMemo as useMemo10,
|
|
5951
|
-
useState as
|
|
5946
|
+
useState as useState10
|
|
5952
5947
|
} from "react";
|
|
5953
5948
|
|
|
5954
5949
|
// src/client-urls/navigation.ts
|
|
5955
|
-
import { startTransition as
|
|
5950
|
+
import { startTransition as startTransition3 } from "react";
|
|
5956
5951
|
|
|
5957
5952
|
// src/client-urls/client-root.tsx
|
|
5958
5953
|
import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
@@ -7146,7 +7141,7 @@ function resolveVersionsFrom(table, routerId2) {
|
|
|
7146
7141
|
// package.json
|
|
7147
7142
|
var package_default = {
|
|
7148
7143
|
name: "@rangojs/router",
|
|
7149
|
-
version: "0.
|
|
7144
|
+
version: "0.30.0",
|
|
7150
7145
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
7151
7146
|
keywords: [
|
|
7152
7147
|
"react",
|
package/package.json
CHANGED
|
@@ -450,16 +450,12 @@ function StockPanel() {
|
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
function SaveStock({ sku }: { sku: string }) {
|
|
453
|
+
// Kept until the next save starts.
|
|
453
454
|
const { revalidationError } = useAction(saveStock);
|
|
454
|
-
const [stale, setStale] = useState(false);
|
|
455
|
-
// useAction resets to idle once the action has settled: keep the notice.
|
|
456
|
-
useEffect(() => {
|
|
457
|
-
if (revalidationError) setStale(true);
|
|
458
|
-
}, [revalidationError]);
|
|
459
455
|
return (
|
|
460
456
|
<form action={saveStock.bind(null, sku)}>
|
|
461
457
|
<button>Save</button>
|
|
462
|
-
{
|
|
458
|
+
{revalidationError != null && (
|
|
463
459
|
<p role="status">Saved. The stock count could not be refreshed.</p>
|
|
464
460
|
)}
|
|
465
461
|
</form>
|
|
@@ -734,8 +730,10 @@ a hard load of the target URL renders the full route.
|
|
|
734
730
|
values during SSR (live request seeds the SSR store; on ppr routes the
|
|
735
731
|
capture/resume renders seed the shell key's own sorted search — search is
|
|
736
732
|
part of shell identity, so static-part reads are legal).
|
|
737
|
-
`useNavigation`/`useLinkStatus` report the canonical nav
|
|
738
|
-
|
|
733
|
+
`useNavigation`/`useLinkStatus` report the canonical nav (while the frame
|
|
734
|
+
shows, `useNavigation()` is `state: "loading"` with `location` the
|
|
735
|
+
committed URL, even though the URL hooks already report the frame's
|
|
736
|
+
destination), but a reader inside optimistically-swapped content (destination WITH `loading()`)
|
|
739
737
|
unmounts at click — put status readers in surviving chrome. Errors: wrap
|
|
740
738
|
throwers in the client `ErrorBoundary`; `useFetchLoader` works unchanged
|
|
741
739
|
(route-independent lane; `revalidate()` deliberately not consulted).
|
package/skills/hooks/SKILL.md
CHANGED
|
@@ -86,7 +86,7 @@ the one for your case.
|
|
|
86
86
|
| `useHref()` | Mount-aware href | `(path) => string` |
|
|
87
87
|
| `useMount()` | Current include() mount path | `string` |
|
|
88
88
|
| `useReverse()` | Local reverse for imported routes | `(name, params?, search?) => string` |
|
|
89
|
-
| `useNavigation()` | Reactive navigation state | state, location, pendingUrl, isStreaming
|
|
89
|
+
| `useNavigation()` | Reactive navigation state | state, location, pendingUrl, isNavigating, isStreaming |
|
|
90
90
|
| `useRouter()` | Stable router actions | push, replace, refresh, prefetch, back, forward |
|
|
91
91
|
| `useSegments()` | URL path & segment IDs | path, segmentIds, location |
|
|
92
92
|
| `useLinkStatus()` | Link pending state | { pending } |
|
package/skills/hooks/data.md
CHANGED
|
@@ -14,7 +14,8 @@ function ProductPrice() {
|
|
|
14
14
|
|
|
15
15
|
// data: T (guaranteed - throws if not in context)
|
|
16
16
|
// isLoading: boolean (refetch/load() states + a held navigation replacing
|
|
17
|
-
// this data — NOT the initial streamed read
|
|
17
|
+
// this data — NOT the initial streamed read, and NOT a server action
|
|
18
|
+
// re-running the loader)
|
|
18
19
|
// error: Error | null
|
|
19
20
|
|
|
20
21
|
return <span>${data.price}</span>;
|
|
@@ -30,6 +31,12 @@ component renders, `data` is present. (On document loads a loader registered
|
|
|
30
31
|
with `{ ssr: false }` is already settled at first paint, so its
|
|
31
32
|
reads never suspend there — see `/loader`.)
|
|
32
33
|
|
|
34
|
+
**`isLoading` covers navigations and `load()` only.** When a server action
|
|
35
|
+
re-runs the loader (its revalidation), `isLoading` stays `false`: the action's
|
|
36
|
+
own progress is `useAction()`'s `state`, or `useNavigation().state`, which is
|
|
37
|
+
`"loading"` while an action runs. Read those for an action spinner and keep
|
|
38
|
+
`isLoading` for "this data is being replaced by a navigation or refresh".
|
|
39
|
+
|
|
33
40
|
**A held navigation flags the data it keeps on screen.** When a navigation
|
|
34
41
|
keeps the current content visible while the loader re-runs (a `transition()`
|
|
35
42
|
same-route nav, a same-structure search/filter nav), the reader that is still
|
|
@@ -50,7 +50,7 @@ page's values over the page being left:
|
|
|
50
50
|
| ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
51
|
| Document load | The values the HTML was rendered from. `push(promise)` and `.defer()` are awaited during SSR, so the document ships the resolved value. On a `ppr` shell HIT that holds for the handlers' and the `ssr: false` loaders' pushes, which the shell baked; a live-lane loader's push arrives right after hydration (see `/ppr`). |
|
|
52
52
|
| Navigation to a page that commits at once (a cold click that mounts new segments) | The destination's values, in the page's commit; the page's effects read them |
|
|
53
|
-
| Navigation to a page React holds in a transition (`transition()`, same-route search change, fully prefetched click, a handler that awaits) | The page being left keeps its values; the destination's land
|
|
53
|
+
| Navigation to a page React holds in a transition (`transition()`, same-route search change, fully prefetched click, a handler that awaits) | The page being left keeps its values; the destination's land right after the page's commit: without view transitions in the same task, before paint; with view transitions when the transition's `ready` resolves |
|
|
54
54
|
| `push(promise)` or `.defer()` on a client navigation | The previous value, then the new one when the promise resolves, possibly well after the page |
|
|
55
55
|
|
|
56
56
|
You never see a pending value, a blank, or the destination's title over the old
|
|
@@ -193,7 +193,41 @@ tracking actions called outside a `<form action={...}>` flow.
|
|
|
193
193
|
### useAction()
|
|
194
194
|
|
|
195
195
|
Track the lifecycle of a server action, wherever on the page it was invoked.
|
|
196
|
-
If the same action fires several times, the hook reports the latest call
|
|
196
|
+
If the same action fires several times, the hook reports the latest call: the
|
|
197
|
+
last one to start.
|
|
198
|
+
|
|
199
|
+
- Once the call settles, `state` is `idle` and the rest of its state
|
|
200
|
+
(`result`, `error`, `revalidationError`, `payload`, `actionId`) stays until
|
|
201
|
+
the same action is called again: through navigations and Back/Forward, and
|
|
202
|
+
in a component that mounts after the call settled. Every reader of the
|
|
203
|
+
same action (a function or a full id) reads the same call; a string reader
|
|
204
|
+
follows the latest call of any action with that export name. The next
|
|
205
|
+
call reads `loading` with none of them, then its own. A document load
|
|
206
|
+
starts with nothing.
|
|
207
|
+
- When the latest call returns before an earlier one, the hook shows its
|
|
208
|
+
result. The earlier call settling afterwards changes nothing the hook
|
|
209
|
+
reports, a result or an error alike. That call still runs to its end: its
|
|
210
|
+
promise resolves or rejects for its caller, its revalidation re-renders the
|
|
211
|
+
page, and an error-boundary response replaces it.
|
|
212
|
+
- A latest call that was aborted reports nothing: `idle`, with no result or
|
|
213
|
+
error. The hook does not fall back to an earlier call. The router aborts
|
|
214
|
+
the other calls in flight when a call's response is an error boundary.
|
|
215
|
+
- `useNavigation()` stays `loading` while any call runs.
|
|
216
|
+
|
|
217
|
+
Which calls count as the same action depends on what you pass:
|
|
218
|
+
|
|
219
|
+
- A function, or a full id (`"src/actions/cart.ts#addToCart"`): that action
|
|
220
|
+
only.
|
|
221
|
+
- A string name (`"addToCart"`): every action with that export name, in any
|
|
222
|
+
`"use server"` file. The hook follows the latest call among all of them.
|
|
223
|
+
|
|
224
|
+
The hook tracks one call at a time, not one per argument. Every
|
|
225
|
+
`AddToCartButton` below reads the same `useAction(addToCart)`, so each shows
|
|
226
|
+
the latest call from any button, whatever its `productId`. A reader that
|
|
227
|
+
filters on `payload` (`payload?.[0] === productId`) cannot track an earlier
|
|
228
|
+
call once a later one starts: from then on the hook reports the later call
|
|
229
|
+
only. For per-call state, keep it with the call: `useActionState` or
|
|
230
|
+
`useTransition` in the component that makes it.
|
|
197
231
|
|
|
198
232
|
```tsx
|
|
199
233
|
"use client";
|
|
@@ -231,6 +265,10 @@ const isLoading = useAction(addToCart, (s) => s.state === "loading");
|
|
|
231
265
|
const isAdding = useAction("addToCart", (s) => s.state === "loading");
|
|
232
266
|
```
|
|
233
267
|
|
|
268
|
+
Each state change commits urgently, so it shows at once, also while React
|
|
269
|
+
holds a navigation's page. It starts no transition of its own, so it never
|
|
270
|
+
takes that page's transition types.
|
|
271
|
+
|
|
234
272
|
Pass an action imported directly into the client component, or its export
|
|
235
273
|
name as a string. An action received as a prop from a server component has
|
|
236
274
|
lost the metadata `useAction` needs and throws; use the string form there.
|