react-router 8.4.0 → 8.5.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/CHANGELOG.md +51 -1
- package/dist/development/dom-export.d.ts +2 -1
- package/dist/development/dom-export.js +1 -1
- package/dist/development/index-react-server-client.js +2 -2
- package/dist/development/index-react-server.d.ts +583 -165
- package/dist/development/index-react-server.js +27 -19
- package/dist/development/index.d.ts +9 -8
- package/dist/development/index.js +5 -4
- package/dist/development/lib/actions.js +1 -1
- package/dist/development/lib/components.js +22 -3
- package/dist/development/lib/context.d.ts +6 -1
- package/dist/development/lib/context.js +1 -1
- package/dist/development/lib/dom/dom.d.ts +3 -0
- package/dist/development/lib/dom/dom.js +1 -1
- package/dist/development/lib/dom/global.d.ts +35 -0
- package/dist/development/lib/dom/lib.d.ts +34 -9
- package/dist/development/lib/dom/lib.js +17 -6
- package/dist/development/lib/dom/server.js +2 -2
- package/dist/development/lib/dom/ssr/components.js +12 -9
- package/dist/development/lib/dom/ssr/data.js +1 -1
- package/dist/development/lib/dom/ssr/entry.d.ts +9 -2
- package/dist/development/lib/dom/ssr/errorBoundaries.js +1 -1
- package/dist/development/lib/dom/ssr/fallback.js +1 -1
- package/dist/development/lib/dom/ssr/fog-of-war.d.ts +3 -2
- package/dist/development/lib/dom/ssr/fog-of-war.js +14 -11
- package/dist/development/lib/dom/ssr/hydration.js +1 -1
- package/dist/development/lib/dom/ssr/invariant.js +1 -1
- package/dist/development/lib/dom/ssr/links.js +1 -1
- package/dist/development/lib/dom/ssr/markup.js +1 -1
- package/dist/development/lib/dom/ssr/route-discovery.d.ts +120 -0
- package/dist/development/lib/dom/ssr/route-discovery.js +415 -0
- package/dist/development/lib/dom/ssr/routeModules.js +1 -1
- package/dist/development/lib/dom/ssr/routes-test-stub.js +2 -2
- package/dist/development/lib/dom/ssr/routes.d.ts +2 -2
- package/dist/development/lib/dom/ssr/routes.js +8 -8
- package/dist/development/lib/dom/ssr/server.js +3 -1
- package/dist/development/lib/dom/ssr/single-fetch.d.ts +28 -2
- package/dist/development/lib/dom/ssr/single-fetch.js +73 -55
- package/dist/development/lib/dom-export/dom-router-provider.js +1 -1
- package/dist/development/lib/dom-export/hydrated-router.d.ts +28 -0
- package/dist/development/lib/dom-export/hydrated-router.js +32 -12
- package/dist/development/lib/errors.js +1 -1
- package/dist/development/lib/hooks.d.ts +3 -3
- package/dist/development/lib/hooks.js +4 -4
- package/dist/development/lib/href.js +1 -1
- package/dist/development/lib/router/history.js +1 -1
- package/dist/development/lib/router/instrumentation.js +1 -1
- package/dist/development/lib/router/matcher-route-pattern.js +1 -1
- package/dist/development/lib/router/matcher-route-pattern.preload.js +1 -1
- package/dist/development/lib/router/matcher.js +1 -1
- package/dist/development/lib/router/navigation.js +1 -1
- package/dist/development/lib/router/router.d.ts +6 -0
- package/dist/development/lib/router/router.js +157 -65
- package/dist/development/lib/router/url.js +1 -1
- package/dist/development/lib/router/utils.d.ts +24 -4
- package/dist/development/lib/router/utils.js +4 -3
- package/dist/development/lib/rsc/browser.js +2 -2
- package/dist/development/lib/rsc/errorBoundaries.js +1 -1
- package/dist/development/lib/rsc/html-stream/browser.js +1 -1
- package/dist/development/lib/rsc/html-stream/server.js +24 -12
- package/dist/development/lib/rsc/route-modules.js +1 -1
- package/dist/development/lib/rsc/server.rsc.d.ts +3 -2
- package/dist/development/lib/rsc/server.ssr.js +1 -1
- package/dist/development/lib/server-runtime/build.d.ts +14 -0
- package/dist/development/lib/server-runtime/cookies.js +1 -1
- package/dist/development/lib/server-runtime/crypto.js +1 -1
- package/dist/development/lib/server-runtime/data.js +1 -1
- package/dist/development/lib/server-runtime/dev.js +1 -1
- package/dist/development/lib/server-runtime/entry.js +1 -1
- package/dist/development/lib/server-runtime/errors.js +1 -1
- package/dist/development/lib/server-runtime/headers.js +1 -1
- package/dist/development/lib/server-runtime/invariant.js +1 -1
- package/dist/development/lib/server-runtime/mode.js +1 -1
- package/dist/development/lib/server-runtime/routeMatching.js +1 -1
- package/dist/development/lib/server-runtime/routes.d.ts +1 -1
- package/dist/development/lib/server-runtime/routes.js +1 -1
- package/dist/development/lib/server-runtime/server.js +12 -4
- package/dist/development/lib/server-runtime/serverHandoff.d.ts +17 -0
- package/dist/development/lib/server-runtime/serverHandoff.js +1 -1
- package/dist/development/lib/server-runtime/sessions/cookieStorage.js +1 -1
- package/dist/development/lib/server-runtime/sessions/memoryStorage.js +1 -1
- package/dist/development/lib/server-runtime/sessions.js +1 -1
- package/dist/development/lib/server-runtime/single-fetch.js +1 -1
- package/dist/development/lib/server-runtime/urls.js +1 -1
- package/dist/development/lib/server-runtime/warnings.js +1 -1
- package/dist/development/lib/types/internal.js +1 -1
- package/dist/development/lib/types/route-data.d.ts +2 -2
- package/dist/development/lib/types/route-module-annotations.d.ts +1 -1
- package/dist/development/route-pattern.js +1 -1
- package/dist/development/vendor/turbo-stream-v2/flatten.js +1 -1
- package/dist/development/vendor/turbo-stream-v2/turbo-stream.js +1 -1
- package/dist/development/vendor/turbo-stream-v2/unflatten.js +1 -1
- package/dist/development/vendor/turbo-stream-v2/utils.js +1 -1
- package/dist/production/dom-export.d.ts +2 -1
- package/dist/production/dom-export.js +1 -1
- package/dist/production/index-react-server-client.js +2 -2
- package/dist/production/index-react-server.d.ts +583 -165
- package/dist/production/index-react-server.js +27 -19
- package/dist/production/index.d.ts +9 -8
- package/dist/production/index.js +5 -4
- package/dist/production/lib/actions.js +1 -1
- package/dist/production/lib/components.js +22 -3
- package/dist/production/lib/context.d.ts +6 -1
- package/dist/production/lib/context.js +1 -1
- package/dist/production/lib/dom/dom.d.ts +3 -0
- package/dist/production/lib/dom/dom.js +1 -1
- package/dist/production/lib/dom/global.d.ts +35 -0
- package/dist/production/lib/dom/lib.d.ts +34 -9
- package/dist/production/lib/dom/lib.js +17 -6
- package/dist/production/lib/dom/server.js +2 -2
- package/dist/production/lib/dom/ssr/components.js +12 -9
- package/dist/production/lib/dom/ssr/data.js +1 -1
- package/dist/production/lib/dom/ssr/entry.d.ts +9 -2
- package/dist/production/lib/dom/ssr/errorBoundaries.js +1 -1
- package/dist/production/lib/dom/ssr/fallback.js +1 -1
- package/dist/production/lib/dom/ssr/fog-of-war.d.ts +3 -2
- package/dist/production/lib/dom/ssr/fog-of-war.js +14 -11
- package/dist/production/lib/dom/ssr/hydration.js +1 -1
- package/dist/production/lib/dom/ssr/invariant.js +1 -1
- package/dist/production/lib/dom/ssr/links.js +1 -1
- package/dist/production/lib/dom/ssr/markup.js +1 -1
- package/dist/production/lib/dom/ssr/route-discovery.d.ts +120 -0
- package/dist/production/lib/dom/ssr/route-discovery.js +415 -0
- package/dist/production/lib/dom/ssr/routeModules.js +1 -1
- package/dist/production/lib/dom/ssr/routes-test-stub.js +2 -2
- package/dist/production/lib/dom/ssr/routes.d.ts +2 -2
- package/dist/production/lib/dom/ssr/routes.js +8 -8
- package/dist/production/lib/dom/ssr/server.js +3 -1
- package/dist/production/lib/dom/ssr/single-fetch.d.ts +28 -2
- package/dist/production/lib/dom/ssr/single-fetch.js +73 -55
- package/dist/production/lib/dom-export/dom-router-provider.js +1 -1
- package/dist/production/lib/dom-export/hydrated-router.d.ts +28 -0
- package/dist/production/lib/dom-export/hydrated-router.js +32 -12
- package/dist/production/lib/errors.js +1 -1
- package/dist/production/lib/hooks.d.ts +3 -3
- package/dist/production/lib/hooks.js +3 -3
- package/dist/production/lib/href.js +1 -1
- package/dist/production/lib/router/history.js +1 -1
- package/dist/production/lib/router/instrumentation.js +1 -1
- package/dist/production/lib/router/matcher-route-pattern.js +1 -1
- package/dist/production/lib/router/matcher-route-pattern.preload.js +1 -1
- package/dist/production/lib/router/matcher.js +1 -1
- package/dist/production/lib/router/navigation.js +1 -1
- package/dist/production/lib/router/router.d.ts +6 -0
- package/dist/production/lib/router/router.js +157 -65
- package/dist/production/lib/router/url.js +1 -1
- package/dist/production/lib/router/utils.d.ts +24 -4
- package/dist/production/lib/router/utils.js +4 -3
- package/dist/production/lib/rsc/browser.js +2 -2
- package/dist/production/lib/rsc/errorBoundaries.js +1 -1
- package/dist/production/lib/rsc/html-stream/browser.js +1 -1
- package/dist/production/lib/rsc/html-stream/server.js +24 -12
- package/dist/production/lib/rsc/route-modules.js +1 -1
- package/dist/production/lib/rsc/server.rsc.d.ts +3 -2
- package/dist/production/lib/rsc/server.ssr.js +1 -1
- package/dist/production/lib/server-runtime/build.d.ts +14 -0
- package/dist/production/lib/server-runtime/cookies.js +1 -1
- package/dist/production/lib/server-runtime/crypto.js +1 -1
- package/dist/production/lib/server-runtime/data.js +1 -1
- package/dist/production/lib/server-runtime/dev.js +1 -1
- package/dist/production/lib/server-runtime/entry.js +1 -1
- package/dist/production/lib/server-runtime/errors.js +1 -1
- package/dist/production/lib/server-runtime/headers.js +1 -1
- package/dist/production/lib/server-runtime/invariant.js +1 -1
- package/dist/production/lib/server-runtime/mode.js +1 -1
- package/dist/production/lib/server-runtime/routeMatching.js +1 -1
- package/dist/production/lib/server-runtime/routes.d.ts +1 -1
- package/dist/production/lib/server-runtime/routes.js +1 -1
- package/dist/production/lib/server-runtime/server.js +12 -4
- package/dist/production/lib/server-runtime/serverHandoff.d.ts +17 -0
- package/dist/production/lib/server-runtime/serverHandoff.js +1 -1
- package/dist/production/lib/server-runtime/sessions/cookieStorage.js +1 -1
- package/dist/production/lib/server-runtime/sessions/memoryStorage.js +1 -1
- package/dist/production/lib/server-runtime/sessions.js +1 -1
- package/dist/production/lib/server-runtime/single-fetch.js +1 -1
- package/dist/production/lib/server-runtime/urls.js +1 -1
- package/dist/production/lib/server-runtime/warnings.js +1 -1
- package/dist/production/lib/types/internal.js +1 -1
- package/dist/production/lib/types/route-data.d.ts +2 -2
- package/dist/production/lib/types/route-module-annotations.d.ts +1 -1
- package/dist/production/route-pattern.js +1 -1
- package/dist/production/vendor/turbo-stream-v2/flatten.js +1 -1
- package/dist/production/vendor/turbo-stream-v2/turbo-stream.js +1 -1
- package/dist/production/vendor/turbo-stream-v2/unflatten.js +1 -1
- package/dist/production/vendor/turbo-stream-v2/utils.js +1 -1
- package/docs/explanation/lazy-route-discovery.md +4 -0
- package/docs/explanation/sessions-and-cookies.md +108 -4
- package/docs/how-to/custom-fetch.md +218 -0
- package/docs/how-to/data-strategy.md +23 -1
- package/docs/how-to/middleware.md +1 -0
- package/docs/how-to/optimize-revalidation.md +2 -2
- package/docs/how-to/pre-rendering.md +23 -1
- package/docs/how-to/route-discovery.md +145 -0
- package/docs/how-to/spa.md +2 -0
- package/docs/how-to/view-transitions.md +80 -13
- package/docs/start/framework/navigating.md +5 -9
- package/docs/start/framework/rendering.md +37 -0
- package/package.json +9 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Custom Fetch
|
|
3
|
+
unstable: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Custom Fetch
|
|
7
|
+
|
|
8
|
+
[MODES: framework]
|
|
9
|
+
|
|
10
|
+
<docs-warning>This feature is experimental and may change in minor or patch releases. It is not supported in RSC Framework Mode.</docs-warning>
|
|
11
|
+
|
|
12
|
+
The `unstable_fetch` prop on [`HydratedRouter`](../api/framework-routers/HydratedRouter) lets you customize the JavaScript-issued requests React Router makes for server loader/action data and lazy route discovery. Use it to add headers, customize fetch options, or apply a request policy based on the operation that initiated the request.
|
|
13
|
+
|
|
14
|
+
This is a Framework Mode API. In Data Mode, you control fetching in your loaders/actions or through a [`dataStrategy`](./data-strategy#request-metadata-unstable).
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
If you don't have an [`app/entry.client.tsx`](../api/framework-conventions/entry.client.tsx), reveal the default entry:
|
|
19
|
+
|
|
20
|
+
```shellscript nonumber
|
|
21
|
+
npx react-router reveal entry.client
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Pass a custom implementation to `HydratedRouter`:
|
|
25
|
+
|
|
26
|
+
```tsx filename=app/entry.client.tsx
|
|
27
|
+
import { startTransition, StrictMode } from "react";
|
|
28
|
+
import { hydrateRoot } from "react-dom/client";
|
|
29
|
+
import {
|
|
30
|
+
HydratedRouter,
|
|
31
|
+
type unstable_RouterFetch,
|
|
32
|
+
} from "react-router/dom";
|
|
33
|
+
|
|
34
|
+
const customFetch: unstable_RouterFetch = (
|
|
35
|
+
request,
|
|
36
|
+
context,
|
|
37
|
+
) => {
|
|
38
|
+
console.log(context.type, request.method, request.url);
|
|
39
|
+
return fetch(request);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
startTransition(() => {
|
|
43
|
+
hydrateRoot(
|
|
44
|
+
document,
|
|
45
|
+
<StrictMode>
|
|
46
|
+
<HydratedRouter unstable_fetch={customFetch} />
|
|
47
|
+
</StrictMode>,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The callback receives a constructed `Request`, including its URL, headers, body, and abort signal, and must return a `Promise<Response>`. Pass that request to the browser's `fetch` to preserve those settings, including cancellation.
|
|
53
|
+
|
|
54
|
+
`HydratedRouter` creates a singleton router and reads `unstable_fetch` only during initialization. Changing the prop later has no effect. If your implementation needs changing values such as authentication tokens, read current application state inside the callback instead of capturing values from a component render.
|
|
55
|
+
|
|
56
|
+
## Request Context
|
|
57
|
+
|
|
58
|
+
The [`unstable_RouterFetchContext`](https://api.reactrouter.com/v8/types/react-router.dom.unstable_RouterFetchContext.html) type is exported from `react-router/dom` and has the following shape:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import type { NavigationType } from "react-router";
|
|
62
|
+
|
|
63
|
+
type unstable_RouterFetchContext =
|
|
64
|
+
| { type: "manifest" }
|
|
65
|
+
| {
|
|
66
|
+
type: "navigation";
|
|
67
|
+
fetcherKey: string | null;
|
|
68
|
+
navigationType: NavigationType;
|
|
69
|
+
}
|
|
70
|
+
| {
|
|
71
|
+
type: "initialization" | "fetcher" | "revalidation";
|
|
72
|
+
fetcherKey: string | null;
|
|
73
|
+
navigationType: null;
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`type` identifies the operation that initiated the request:
|
|
78
|
+
|
|
79
|
+
- `"manifest"`: Lazy route discovery requests. These have no `fetcherKey` or `navigationType`.
|
|
80
|
+
- `"initialization"`: Data loading during router initialization, such as initial loader requests in API-only Mode.
|
|
81
|
+
- `"navigation"`: Data requests initiated by a navigation, including action submissions and the subsequent loader revalidation.
|
|
82
|
+
- `"fetcher"`: Requests initiated by a fetcher, including the loader revalidation after a fetcher action.
|
|
83
|
+
- `"revalidation"`: Requests initiated by an explicit revalidation, such as `useRevalidator().revalidate()`.
|
|
84
|
+
|
|
85
|
+
`fetcherKey` identifies the fetcher targeted by a data request, or is `null` for route loader/action requests. A fetcher reloaded as part of a navigation or explicit revalidation retains that initiating `type` and also includes its fetcher key. Fetchers reloaded after an initialization redirect likewise retain the `"initialization"` type and include their fetcher key.
|
|
86
|
+
|
|
87
|
+
`navigationType` is `"PUSH"`, `"REPLACE"`, or `"POP"` for navigation-initiated data requests, including fetchers reloaded by that navigation. It is `null` for initialization, standalone fetcher operations, and explicit revalidation. This is the history action for the in-flight navigation, not the previously committed location.
|
|
88
|
+
|
|
89
|
+
## Examples
|
|
90
|
+
|
|
91
|
+
Each example below can be passed to `HydratedRouter` as its `unstable_fetch` prop.
|
|
92
|
+
|
|
93
|
+
### Authentication and Custom Headers
|
|
94
|
+
|
|
95
|
+
Read the current token at request time so token changes don't require replacing the callback:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import type { unstable_RouterFetch } from "react-router/dom";
|
|
99
|
+
import { authStore } from "./auth";
|
|
100
|
+
|
|
101
|
+
const customFetch: unstable_RouterFetch = (request) => {
|
|
102
|
+
let headers = new Headers(request.headers);
|
|
103
|
+
let token = authStore.getAccessToken();
|
|
104
|
+
|
|
105
|
+
if (token) {
|
|
106
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
headers.set("X-App-Version", "1.0");
|
|
110
|
+
return fetch(request, { headers });
|
|
111
|
+
};
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Here, `authStore` is your application's authentication store. Only send credentials to trusted origins. Adding custom headers to cross-origin requests may require a CORS preflight and corresponding server configuration.
|
|
115
|
+
|
|
116
|
+
### Cross-Origin Credentials
|
|
117
|
+
|
|
118
|
+
In [API-only Mode](../start/framework/rendering#using-a-separate-api-origin), use `unstable_apiServerOrigin` to configure a separate API origin. React Router applies that origin before calling your custom fetch, and also uses it for native data prefetches.
|
|
119
|
+
|
|
120
|
+
The origin setting does not enable cross-origin cookies. If you need them, customize the credential policy for the trusted API origin:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import type { unstable_RouterFetch } from "react-router/dom";
|
|
124
|
+
|
|
125
|
+
const customFetch: unstable_RouterFetch = (request) => {
|
|
126
|
+
let isApiRequest =
|
|
127
|
+
new URL(request.url).origin ===
|
|
128
|
+
"https://api.example.com";
|
|
129
|
+
|
|
130
|
+
return fetch(request, {
|
|
131
|
+
credentials: isApiRequest
|
|
132
|
+
? "include"
|
|
133
|
+
: request.credentials,
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The API server must allow credentialed CORS requests from your client origin. Browser cookie policies still apply. Native data prefetches do not use this callback, so don't rely on it to configure their credentials.
|
|
139
|
+
|
|
140
|
+
### Back/Forward Traversal Cache
|
|
141
|
+
|
|
142
|
+
Use `navigationType` to prefer the browser's HTTP cache when navigating back or forward, while retaining the normal cache policy for other requests:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import type { unstable_RouterFetch } from "react-router/dom";
|
|
146
|
+
|
|
147
|
+
const customFetch: unstable_RouterFetch = (
|
|
148
|
+
request,
|
|
149
|
+
context,
|
|
150
|
+
) => {
|
|
151
|
+
let isTraversal =
|
|
152
|
+
import.meta.env.PROD &&
|
|
153
|
+
request.method === "GET" &&
|
|
154
|
+
context.type === "navigation" &&
|
|
155
|
+
context.navigationType === "POP";
|
|
156
|
+
|
|
157
|
+
return fetch(request, {
|
|
158
|
+
cache: isTraversal ? "force-cache" : "default",
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
This example enables the policy in production only. It includes GET requests for fetchers reloaded by a back/forward navigation, but not standalone fetcher loads or explicit revalidations.
|
|
164
|
+
|
|
165
|
+
The [`force-cache` policy](https://fetch.spec.whatwg.org/#concept-request-cache-mode) uses a matching HTTP cache entry even if it is stale, falling back to the network when no entry exists. Your server's responses must be cacheable; `Cache-Control: no-store` responses aren't stored.
|
|
166
|
+
|
|
167
|
+
This is not a router-managed data cache. React Router still performs its request and response decoding, and this policy adds no mutation or authentication invalidation guarantees. Only use it when serving potentially stale data is appropriate for your application.
|
|
168
|
+
|
|
169
|
+
### Retrying Safe Requests
|
|
170
|
+
|
|
171
|
+
You can retry a safe GET after a network failure without retrying action submissions or canceled requests:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import type { unstable_RouterFetch } from "react-router/dom";
|
|
175
|
+
|
|
176
|
+
const customFetch: unstable_RouterFetch = async (
|
|
177
|
+
request,
|
|
178
|
+
) => {
|
|
179
|
+
if (request.method !== "GET") {
|
|
180
|
+
return fetch(request);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
return await fetch(request.clone());
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (
|
|
187
|
+
request.signal.aborted ||
|
|
188
|
+
(error instanceof Error &&
|
|
189
|
+
error.name === "AbortError")
|
|
190
|
+
) {
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Retry once, retaining the original abort signal.
|
|
195
|
+
return fetch(request);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
This assumes your GET endpoints are safe to repeat. It retries a rejected fetch, not HTTP error responses such as `500`. Don't automatically retry mutations: the server may have processed an action even when the client didn't receive its response.
|
|
201
|
+
|
|
202
|
+
## Limitations
|
|
203
|
+
|
|
204
|
+
### Requests Outside Custom Fetch
|
|
205
|
+
|
|
206
|
+
Custom fetch does not intercept the initial document request, server-side loader execution, static assets, or fetches you write in your own application code, including in `clientLoader`/`clientAction`. Calls to their `serverLoader`/`serverAction` helpers do use custom fetch when they issue server data requests.
|
|
207
|
+
|
|
208
|
+
Data prefetches from `<Link prefetch>`, `<NavLink prefetch>`, and [`PrefetchPageLinks`](../api/components/PrefetchPageLinks) use native `<link rel="prefetch">` elements and bypass custom fetch. If a request requires custom headers or other transformations, use `prefetch="none"` on links and avoid rendering `PrefetchPageLinks` for those routes.
|
|
209
|
+
|
|
210
|
+
### Inspecting Request Bodies
|
|
211
|
+
|
|
212
|
+
If you need a form field such as `intent` to choose request headers, read it from `await request.clone().formData()` for form-encoded submissions, then send the original request. Reading `request.formData()` directly consumes the body so it can't subsequently be sent unchanged.
|
|
213
|
+
|
|
214
|
+
Only parse bodies with a compatible encoding. Cloning and reading a body can buffer data, so avoid doing this unnecessarily, especially for large file uploads. Adding headers or changing fetch options does not require reading the body.
|
|
215
|
+
|
|
216
|
+
### Response Compatibility
|
|
217
|
+
|
|
218
|
+
Return the response from `fetch` unchanged. You can inspect its status and headers, but you should not consume its body or try to decode, modify, or re-encode it. React Router handles response decoding and expects its manifest/data protocol, including Turbo Stream-encoded loader/action data.
|
|
@@ -23,7 +23,9 @@ A custom `dataStrategy` receives the `loader`/`action` arguments (`request`, `pa
|
|
|
23
23
|
|
|
24
24
|
- `matches`: An array of `DataStrategyMatch` instances for the routes matched by the current `request`
|
|
25
25
|
- `runClientMiddleware`: A helper function to run the middleware for the matched routes
|
|
26
|
-
- `fetcherKey`: The fetcher
|
|
26
|
+
- `fetcherKey`: The key of the fetcher targeted by this execution, or `null` for route loader/action executions. Fetchers reloaded by a navigation or explicit revalidation also provide their key.
|
|
27
|
+
- `unstable_initiator`: The operation that initiated this execution (see [Request Metadata](#request-metadata-unstable))
|
|
28
|
+
- `unstable_navigationType`: The history action for a navigation-initiated execution, or `null` for other executions
|
|
27
29
|
|
|
28
30
|
A `DataStrategyMatch` is a normal route match plus a few additional fields:
|
|
29
31
|
|
|
@@ -77,6 +79,26 @@ interface DataStrategyResult {
|
|
|
77
79
|
}
|
|
78
80
|
```
|
|
79
81
|
|
|
82
|
+
### Request Metadata (unstable)
|
|
83
|
+
|
|
84
|
+
<docs-warning>`unstable_initiator` and `unstable_navigationType` are experimental and may change in minor or patch releases.</docs-warning>
|
|
85
|
+
|
|
86
|
+
`unstable_initiator` identifies why the data strategy is running:
|
|
87
|
+
|
|
88
|
+
- `"initialization"`: Initial router data loading
|
|
89
|
+
- `"navigation"`: A navigation, including action submissions and subsequent loader revalidation
|
|
90
|
+
- `"fetcher"`: A fetcher operation, including loader revalidation after a fetcher action
|
|
91
|
+
- `"revalidation"`: Explicit revalidation, such as `useRevalidator().revalidate()`
|
|
92
|
+
- `"static"`: Execution by `createStaticHandler` on the server
|
|
93
|
+
|
|
94
|
+
Requests that reload fetchers during a navigation or explicit revalidation retain that initiator while providing the targeted `fetcherKey`. Don't use `fetcherKey` alone to determine what initiated the execution.
|
|
95
|
+
|
|
96
|
+
`unstable_navigationType` is `"PUSH"`, `"REPLACE"`, or `"POP"` for navigation-initiated executions, including fetchers reloaded by that navigation. It is `null` for initialization, standalone fetcher operations, explicit revalidation, and static handler executions. It describes the in-flight navigation rather than the previously committed location.
|
|
97
|
+
|
|
98
|
+
You can use this metadata to choose a loading or caching policy. For example, a Data Mode `dataStrategy` can distinguish back/forward navigations with `unstable_initiator === "navigation" && unstable_navigationType === "POP"`. Your strategy remains responsible for executing the appropriate handlers and returning their results.
|
|
99
|
+
|
|
100
|
+
Framework Mode exposes equivalent request metadata through [`unstable_fetch`](./custom-fetch#request-context), which customizes server requests without replacing loader/action execution.
|
|
101
|
+
|
|
80
102
|
### Calling Route Middleware
|
|
81
103
|
|
|
82
104
|
If you are using `middleware` on your routes, you need to leverage the `callClientMiddleware` helper function to execute `middleware` around your handlers:
|
|
@@ -635,6 +635,7 @@ export async function loader({
|
|
|
635
635
|
[server-client]: #server-vs-client-middleware
|
|
636
636
|
[framework-action]: ../start/framework/route-module#action
|
|
637
637
|
[framework-loader]: ../start/framework/route-module#loader
|
|
638
|
+
[getloadcontext]: ../api/other-api/adapter#react-routerexpress
|
|
638
639
|
[datastrategy]: ../api/data-routers/createBrowserRouter#optsdatastrategy
|
|
639
640
|
[cms-redirect]: #cms-redirect-on-404
|
|
640
641
|
[createContext]: ../api/utils/createContext
|
|
@@ -25,10 +25,10 @@ Prefer targeting a specific action or navigation, and fall back to
|
|
|
25
25
|
The default behavior differs between Framework and Data Modes:
|
|
26
26
|
|
|
27
27
|
- **Framework Mode with SSR**
|
|
28
|
-
- Defaults to opt-out behavior - active loaders are revalidated on navigations and successful submissions ([`Link`][link], [`Form`][form], [`fetcher.submit`]
|
|
28
|
+
- Defaults to opt-out behavior - active loaders are revalidated on navigations and successful submissions ([`Link`][link], [`Form`][form], [`fetcher.submit`][use-fetcher])
|
|
29
29
|
- Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
|
|
30
30
|
- **Framework "SPA Mode" and Data Mode**
|
|
31
|
-
- Defaults to opt-out behavior on successful submissions - active loaders are revalidated on successful submissions ([`Form`][form], [`fetcher.submit`])
|
|
31
|
+
- Defaults to opt-out behavior on successful submissions - active loaders are revalidated on successful submissions ([`Form`][form], [`fetcher.submit`][use-fetcher])
|
|
32
32
|
- Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
|
|
33
33
|
- Defaults to opt-in behavior for GET navigations ([`Link`][link]) - active loaders are only revalidated if their dynamic params changed, or if any search params changed
|
|
34
34
|
- A GET navigation to the _exact_ same URL is treated like a page refresh and all loaders are revalidated.
|
|
@@ -84,10 +84,11 @@ export default {
|
|
|
84
84
|
|
|
85
85
|
## Pre-Rendering with/without a Runtime Server
|
|
86
86
|
|
|
87
|
-
Pre-Rendering can be used
|
|
87
|
+
Pre-Rendering can be used with these `ssr` config values:
|
|
88
88
|
|
|
89
89
|
- Alongside a runtime SSR server with `ssr:true` (the default value)
|
|
90
90
|
- Deployed to a static file server with `ssr:false`
|
|
91
|
+
- Alongside a runtime API server with `ssr:"unstable_api-only"` (experimental)
|
|
91
92
|
|
|
92
93
|
### Pre-rendering with `ssr:true`
|
|
93
94
|
|
|
@@ -223,3 +224,24 @@ When pre-rendering with `ssr:false`, React Router will error at build time if yo
|
|
|
223
224
|
- If you are using a `loader` on a pre-rendered route that has child routes, you will need to make sure the parent `loaderData` can be determined at run-time properly by either:
|
|
224
225
|
- Pre-rendering all child routes so that the parent `loader` can be called at build-time for each child route path and rendered into a `.data` file, or
|
|
225
226
|
- Use a `clientLoader` on the parent that can be called at run-time for non-pre-rendered child paths
|
|
227
|
+
|
|
228
|
+
### Pre-rendering with API-only Mode (unstable)
|
|
229
|
+
|
|
230
|
+
<docs-warning>API-only Mode is experimental and may change in minor or patch releases. It is not supported in RSC Framework Mode.</docs-warning>
|
|
231
|
+
|
|
232
|
+
Use `ssr: "unstable_api-only"` with `prerender` to generate documents at build time while keeping a runtime server for loaders, actions, and route discovery:
|
|
233
|
+
|
|
234
|
+
```ts filename=react-router.config.ts
|
|
235
|
+
import type { Config } from "@react-router/dev/config";
|
|
236
|
+
|
|
237
|
+
export default {
|
|
238
|
+
ssr: "unstable_api-only",
|
|
239
|
+
prerender: ["/"],
|
|
240
|
+
} satisfies Config;
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Document generation follows the same behavior as `ssr:false`: selected paths produce HTML and `.data` files, and other paths use a SPA fallback. The fallback is `__spa-fallback.html` when `/` is prerendered, or `index.html` otherwise.
|
|
244
|
+
|
|
245
|
+
Prerendered pages hydrate using their build-time data. Pages using the fallback fetch server loader data during initialization. Server loaders and actions are available on all routes, including those outside the prerender list, and normal loader revalidation remains enabled.
|
|
246
|
+
|
|
247
|
+
See [API-only Mode](../start/framework/rendering#api-only-mode-unstable) for deployment routing and configuring a separate API origin.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Application-Controlled Route Discovery
|
|
3
|
+
unstable: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Application-Controlled Route Discovery
|
|
7
|
+
|
|
8
|
+
[MODES: framework]
|
|
9
|
+
|
|
10
|
+
<docs-warning>This API is unstable and may change without a major release. It supports conventional Framework Mode; RSC modes are not supported.</docs-warning>
|
|
11
|
+
|
|
12
|
+
Enable the future flag in `react-router.config.ts`:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import type { Config } from "@react-router/dev/config";
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
future: { unstable_customRouteDiscovery: true },
|
|
19
|
+
} satisfies Config;
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Without this flag, React Router keeps its existing discovery implementation and the hook is unavailable. The flag supports conventional Framework Mode only.
|
|
23
|
+
|
|
24
|
+
Keep `routeDiscovery.mode: "lazy"` (the default for SSR) and use `unstable_useRouteDiscovery` to choose when to discover destinations or load the complete route manifest.
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
let { state, discoverRoutes, loadAllRoutes } =
|
|
28
|
+
unstable_useRouteDiscovery({
|
|
29
|
+
onManifestMismatch: async (event) => {
|
|
30
|
+
// Handle mismatch, potentially by loading the entire manifest
|
|
31
|
+
await loadAllRoutes();
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// state: "partial" | "loading" | "complete"
|
|
36
|
+
|
|
37
|
+
// Eagerly discover specific paths
|
|
38
|
+
let result = await discoverRoutes(paths);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Imperative Route Discovery
|
|
42
|
+
|
|
43
|
+
You can control route discovery through `discoverRoutes()` without having to render links/forms on the page - this allows you to eagerly discover routes that are not directly linked from the current UI. Note that discovery only loads route metadata - it does not navigate, run loaders/actions, or prefetch route modules/stylesheets. Paths are batched within the manifest request URL limit and previously discovered paths are skipped.
|
|
44
|
+
|
|
45
|
+
`discoverRoutes(paths, { signal })` accepts application-root paths beginning with `/`. React Router applies the configured `basename` and ignores query strings and hashes. Relative paths and external URLs are not supported.
|
|
46
|
+
|
|
47
|
+
The result is `{ type: "success" }`, `{ type: "version-mismatch" }`, or `{ type: "aborted" }`. Success means the discovery operation completed successfully; it does not guarantee that a request was sent or that each pathname matches a route. Cached paths need no request, and a concurrent full-manifest load can supersede an incremental request failure. Unrecovered request failures and unrelated handler errors reject. Imperative mismatches do not automatically trigger a reload.
|
|
48
|
+
|
|
49
|
+
For example, the cart's checkout link automatically discovers the shipping route. Use the hook to discover later checkout steps when the cart mounts, even though those steps aren't linked from the cart:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { useEffect } from "react";
|
|
53
|
+
import {
|
|
54
|
+
Link,
|
|
55
|
+
unstable_useRouteDiscovery,
|
|
56
|
+
} from "react-router";
|
|
57
|
+
|
|
58
|
+
function Cart() {
|
|
59
|
+
let { discoverRoutes } = unstable_useRouteDiscovery();
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
let controller = new AbortController();
|
|
63
|
+
|
|
64
|
+
discoverRoutes(
|
|
65
|
+
["/checkout/billing", "/checkout/review"],
|
|
66
|
+
{ signal: controller.signal },
|
|
67
|
+
).catch(console.error);
|
|
68
|
+
|
|
69
|
+
return () => controller.abort();
|
|
70
|
+
}, [discoverRoutes]);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
{/* Render cart items and totals here. */}
|
|
75
|
+
<Link to="/checkout/shipping">
|
|
76
|
+
Proceed to checkout
|
|
77
|
+
</Link>
|
|
78
|
+
</>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Completing discovery
|
|
84
|
+
|
|
85
|
+
`loadAllRoutes()` loads the running client's versioned manifest asset to complete its route tree, even if that client is stale. This merges all missing routes into the running router and thus prevents further incremental discovery. The exposed `version` remains the version of the running client. With `routeDiscovery.mode: "initial"`, SPA Mode, or development's full manifest, the state is already `complete` and the operations are successful no-ops.
|
|
86
|
+
|
|
87
|
+
The shared `state` moves from `partial` to `loading` to `complete`. Concurrent calls share a load, and calls after completion resolve immediately. Failures reject and return the state to `partial`; applications can catch the error and choose whether to reload.
|
|
88
|
+
|
|
89
|
+
Retain the client's versioned manifest **and its referenced assets** for as long as that client is supported. Server routing and loader/action contracts must also remain compatible. Loading build A's manifest does not make an incompatible build B backend safe for build A's client.
|
|
90
|
+
|
|
91
|
+
## Handling manifest mismatches
|
|
92
|
+
|
|
93
|
+
You can provide custom handling for manifest version mismatches to avoid hard reloads, giving you a chance to complete the route tree, or cancel the navigation and provide a prompt for the user to reload (see below). A mismatch indicates different versions, not which version is newer.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
unstable_useRouteDiscovery({
|
|
97
|
+
onManifestMismatch: async (event) => {
|
|
98
|
+
await event.loadAllRoutes();
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
After mismatch handlers settle, surviving operations continue only if the tree is complete. Otherwise navigations and fetchers are canceled and their pending state settles. Canceling a navigation also cancels any pending revalidation - both return to idle and the current page and data remain in place without another loader pass. Similarly, interrupted fetcher loads settle with their existing data. If an action already changed server data, the displayed data may remain stale until a later navigation or explicit revalidation.
|
|
104
|
+
|
|
105
|
+
Router-managed Back/Forward cancellations restore the prior history position. Additional browser Back/Forward presses during restoration can leave the URL and rendered route out of sync or navigate to another entry; preserving the current screen is not guaranteed in that case.
|
|
106
|
+
|
|
107
|
+
Full-manifest load failures remain catchable and do not render route error boundaries, whether the handler calls `event.loadAllRoutes()` or the function returned by the hook. Uncaught full-load failures are reported through `<HydratedRouter onError>` or `console.error`, and the incomplete operation is canceled. Unrelated handler errors follow normal discovery error handling.
|
|
108
|
+
|
|
109
|
+
## Temporary overrides
|
|
110
|
+
|
|
111
|
+
Custom mismatch handlers override built-in recovery. Calling the hook without a handler does not override anything. Removing the last custom mismatch handler restores automatic recovery.
|
|
112
|
+
|
|
113
|
+
Call `event.defaultBehavior()` to explicitly run built-in recovery: eager mismatches warn, navigation mismatches reload the destination, fetcher mismatches reload the current document, and imperative mismatches return without reloading. Repeated calls for one shared mismatch event reuse the same promise. It does not invoke other custom handlers or stop their propagation.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
unstable_useRouteDiscovery({
|
|
117
|
+
async onManifestMismatch(event) {
|
|
118
|
+
if (hasUnsavedChanges) {
|
|
119
|
+
event.stopPropagation();
|
|
120
|
+
setShowReloadDialog(true);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
await event.defaultBehavior();
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Custom handlers run in reverse registration order, with each awaited before the next. `stopPropagation()` prevents earlier handlers from running; it does not complete discovery or allow an operation to continue against an incomplete tree.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
unstable_useRouteDiscovery({
|
|
132
|
+
onManifestMismatch(event) {
|
|
133
|
+
if (hasUnsavedChanges) {
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
setShowReloadDialog(true);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
An editor mounted after a root recovery handler can use this to preserve a draft. Unmounting removes its registration and reveals earlier handlers. Callback updates preserve registration priority. This is registration order, not a guarantee that the deepest component runs first.
|
|
142
|
+
|
|
143
|
+
Eager discovery waits for the initialized route tree to commit so a root handler can intercept the first request. It does not wait for every suspended descendant or guarantee an idle main thread. Discovery needed before handlers can mount retains normal lazy behavior.
|
|
144
|
+
|
|
145
|
+
Without mismatch handlers, existing automatic recovery for eager/navigation/fetcher discovery remains unchanged. Deployment polling and staleness policy remain application-owned.
|
package/docs/how-to/spa.md
CHANGED
|
@@ -22,6 +22,8 @@ Typical Single Page apps send a mostly blank `index.html` template with little m
|
|
|
22
22
|
|
|
23
23
|
<docs-info>SPA Mode is a special form of "Pre-Rendering" that allows you to serve all paths in your application from the same HTML file. Please refer to the [Pre-Rendering](./pre-rendering) guide if you want to do more extensive pre-rendering.</docs-info>
|
|
24
24
|
|
|
25
|
+
<docs-info>If you want a SPA frontend backed by React Router server loaders and actions, see experimental [API-only Mode](../start/framework/rendering#api-only-mode-unstable).</docs-info>
|
|
26
|
+
|
|
25
27
|
## 1. Disable Runtime Server Rendering
|
|
26
28
|
|
|
27
29
|
Server rendering is enabled by default. Set the `ssr` flag to `false` in `react-router.config.ts` to disable it.
|
|
@@ -4,16 +4,78 @@ title: View Transitions
|
|
|
4
4
|
|
|
5
5
|
# View Transitions
|
|
6
6
|
|
|
7
|
-
[MODES: framework, data]
|
|
7
|
+
[MODES: framework, data, declarative]
|
|
8
8
|
|
|
9
9
|
<br/>
|
|
10
10
|
<br/>
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Use React's [`<ViewTransition>`][react-view-transition] component to animate client-side navigations. This requires React and React DOM 19.3 or later.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
<docs-warning>
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
React Router's `viewTransition` props/options, `useViewTransitionState` hook, and `NavLink`'s `isTransitioning` render prop and `transitioning` class are deprecated in favor of React's `<ViewTransition>` component. The existing APIs continue to work, including with older React versions.
|
|
17
|
+
|
|
18
|
+
</docs-warning>
|
|
19
|
+
|
|
20
|
+
## Navigation View Transitions with React
|
|
21
|
+
|
|
22
|
+
Wrap the outlet in a persistent `<ViewTransition>` boundary in a shared layout route. In Framework Mode, this can be your root route's default export; in Data and Declarative modes, use it as the component for a parent route with children:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { ViewTransition } from "react";
|
|
26
|
+
import { Link, Outlet } from "react-router";
|
|
27
|
+
|
|
28
|
+
export default function PageLayout() {
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<nav>
|
|
32
|
+
<Link to="/">Home</Link>
|
|
33
|
+
<Link to="/about">About</Link>
|
|
34
|
+
</nav>
|
|
35
|
+
<ViewTransition>
|
|
36
|
+
<main>
|
|
37
|
+
<Outlet />
|
|
38
|
+
</main>
|
|
39
|
+
</ViewTransition>
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Navigations that update the outlet cross-fade its contents. Keep the boundary mounted across navigations; you don't need to key it by location or remount the route tree. The navigation stays outside the animated region. Other updates inside the boundary can also animate when they run in a React Transition.
|
|
46
|
+
|
|
47
|
+
React Router wraps router state updates in [`React.startTransition`][start-transition] by default, which lets React activate `<ViewTransition>` without additional configuration. React Transitions must remain enabled for navigation view transitions to work: do not set `useTransitions={false}` on your router.
|
|
48
|
+
|
|
49
|
+
This applies to navigations from `<Link>`, `<NavLink>`, and `<Form>`, as well as programmatic navigation with `useNavigate` and `useSubmit`. No additional `startTransition` wrapper is needed.
|
|
50
|
+
|
|
51
|
+
### Migrating from React Router's view transitions
|
|
52
|
+
|
|
53
|
+
- Remove `viewTransition` from `<Link>`, `<NavLink>`, and `<Form>`, and remove `viewTransition: true` from navigation/submission options. React coordinates `document.startViewTransition()` itself; enabling both implementations can interrupt animations.
|
|
54
|
+
- Replace `useViewTransitionState`, `isTransitioning`, and `.transitioning` styles with React's [View Transition classes][react-view-transition-classes]. Those router APIs only track the legacy transitions.
|
|
55
|
+
- For shared elements such as an image moving from a list to a detail route, wrap each side in `<ViewTransition name="...">` with the same unique name. See React's [shared element example][react-shared-elements].
|
|
56
|
+
- Keep React Transitions enabled. `useTransitions={false}` and synchronous updates such as `flushSync` can prevent React's animations.
|
|
57
|
+
|
|
58
|
+
React's component is not a drop-in replacement for every legacy animation. In particular, React [skips view transitions for synchronous updates from `popstate`][react-router-transitions], so browser Back/Forward navigations may not animate. Browsers without View Transitions support still navigate normally.
|
|
59
|
+
|
|
60
|
+
Respect reduced-motion preferences when adding animations:
|
|
61
|
+
|
|
62
|
+
```css
|
|
63
|
+
@media (prefers-reduced-motion: reduce) {
|
|
64
|
+
::view-transition-group(*),
|
|
65
|
+
::view-transition-old(*),
|
|
66
|
+
::view-transition-new(*) {
|
|
67
|
+
animation: none !important;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Legacy View Transitions (Deprecated)
|
|
73
|
+
|
|
74
|
+
The following examples document the deprecated APIs for existing applications. These APIs are available in Framework and Data modes.
|
|
75
|
+
|
|
76
|
+
### Basic View Transition
|
|
77
|
+
|
|
78
|
+
#### 1. Enable view transitions on navigation
|
|
17
79
|
|
|
18
80
|
The simplest way to enable view transitions is by adding the `viewTransition` prop to your `Link`, `NavLink`, or `Form` components. This automatically wraps the navigation update in `document.startViewTransition()`.
|
|
19
81
|
|
|
@@ -25,7 +87,7 @@ The simplest way to enable view transitions is by adding the `viewTransition` pr
|
|
|
25
87
|
|
|
26
88
|
Without any additional CSS, this provides a basic cross-fade animation between pages.
|
|
27
89
|
|
|
28
|
-
|
|
90
|
+
#### 2. Enable view transitions with programmatic navigation
|
|
29
91
|
|
|
30
92
|
When using programmatic navigation with the `useNavigate` hook, you can enable view transitions by passing the `viewTransition: true` option:
|
|
31
93
|
|
|
@@ -51,11 +113,11 @@ This provides the same cross-fade animation as using the `viewTransition` prop o
|
|
|
51
113
|
|
|
52
114
|
For more information on using the View Transitions API, please refer to the ["Smooth transitions with the View Transition API" guide][view-transitions-guide] from the Google Chrome team.
|
|
53
115
|
|
|
54
|
-
|
|
116
|
+
### Image Gallery Example
|
|
55
117
|
|
|
56
118
|
Let's build an image gallery that demonstrates how to trigger and use view transitions. We'll create a list of images that expand into a detail view with smooth animations.
|
|
57
119
|
|
|
58
|
-
|
|
120
|
+
#### 1. Create the image gallery route
|
|
59
121
|
|
|
60
122
|
```tsx filename=routes/image-gallery.tsx
|
|
61
123
|
import { NavLink } from "react-router";
|
|
@@ -91,7 +153,7 @@ export default function ImageGalleryRoute() {
|
|
|
91
153
|
}
|
|
92
154
|
```
|
|
93
155
|
|
|
94
|
-
|
|
156
|
+
#### 2. Add transition styles
|
|
95
157
|
|
|
96
158
|
Define view transition names and animations for elements that should transition smoothly between routes.
|
|
97
159
|
|
|
@@ -127,7 +189,7 @@ Define view transition names and animations for elements that should transition
|
|
|
127
189
|
}
|
|
128
190
|
```
|
|
129
191
|
|
|
130
|
-
|
|
192
|
+
#### 3. Create the image detail route
|
|
131
193
|
|
|
132
194
|
The detail view needs to use the same view transition names to create a seamless animation.
|
|
133
195
|
|
|
@@ -151,7 +213,7 @@ export default function ImageDetailsRoute({
|
|
|
151
213
|
}
|
|
152
214
|
```
|
|
153
215
|
|
|
154
|
-
|
|
216
|
+
#### 4. Add matching transition styles for the detail view
|
|
155
217
|
|
|
156
218
|
```css filename=app.css
|
|
157
219
|
/* Match transition names from the list view */
|
|
@@ -169,11 +231,11 @@ export default function ImageDetailsRoute({
|
|
|
169
231
|
}
|
|
170
232
|
```
|
|
171
233
|
|
|
172
|
-
|
|
234
|
+
### Advanced Usage
|
|
173
235
|
|
|
174
236
|
You can control view transitions more precisely using either render props or the `useViewTransitionState` hook.
|
|
175
237
|
|
|
176
|
-
|
|
238
|
+
#### 1. Using render props
|
|
177
239
|
|
|
178
240
|
```tsx filename=routes/image-gallery.tsx
|
|
179
241
|
<NavLink to={`/image/${idx}`} viewTransition>
|
|
@@ -201,7 +263,7 @@ You can control view transitions more precisely using either render props or the
|
|
|
201
263
|
</NavLink>
|
|
202
264
|
```
|
|
203
265
|
|
|
204
|
-
|
|
266
|
+
#### 2. Using the `useViewTransitionState` hook
|
|
205
267
|
|
|
206
268
|
```tsx filename=routes/image-gallery.tsx
|
|
207
269
|
function NavImage(props: { src: string; idx: number }) {
|
|
@@ -234,4 +296,9 @@ function NavImage(props: { src: string; idx: number }) {
|
|
|
234
296
|
```
|
|
235
297
|
|
|
236
298
|
[view-transitions-api]: https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition
|
|
299
|
+
[react-view-transition]: https://react.dev/reference/react/ViewTransition
|
|
300
|
+
[start-transition]: https://react.dev/reference/react/startTransition
|
|
301
|
+
[react-view-transition-classes]: https://react.dev/reference/react/ViewTransition#view-transition-class
|
|
302
|
+
[react-shared-elements]: https://react.dev/reference/react/ViewTransition#animating-a-shared-element
|
|
303
|
+
[react-router-transitions]: https://react.dev/reference/react/ViewTransition#building-view-transition-enabled-routers
|
|
237
304
|
[view-transitions-guide]: https://developer.chrome.com/docs/web-platform/view-transitions
|