react-router 8.3.0 → 8.4.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 +46 -2
- package/dist/development/dom-export.js +1 -1
- package/dist/development/index-react-server-client.js +1 -1
- package/dist/development/index-react-server.d.ts +50 -48
- package/dist/development/index-react-server.js +96 -43
- package/dist/development/index.d.ts +2 -2
- package/dist/development/index.js +3 -3
- package/dist/development/lib/actions.js +10 -5
- package/dist/development/lib/components.js +50 -10
- package/dist/development/lib/context.d.ts +14 -5
- package/dist/development/lib/context.js +11 -3
- package/dist/development/lib/dom/dom.js +2 -2
- package/dist/development/lib/dom/lib.js +33 -36
- package/dist/development/lib/dom/server.d.ts +6 -1
- package/dist/development/lib/dom/server.js +40 -14
- package/dist/development/lib/dom/ssr/components.js +16 -24
- package/dist/development/lib/dom/ssr/data.js +1 -1
- package/dist/development/lib/dom/ssr/entry.d.ts +4 -0
- 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.js +34 -12
- package/dist/development/lib/dom/ssr/hydration.d.ts +3 -1
- package/dist/development/lib/dom/ssr/hydration.js +6 -4
- 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 +5 -2
- package/dist/development/lib/dom/ssr/routeModules.js +1 -1
- package/dist/development/lib/dom/ssr/routes-test-stub.js +1 -1
- package/dist/development/lib/dom/ssr/routes.js +1 -1
- package/dist/development/lib/dom/ssr/server.js +2 -2
- package/dist/development/lib/dom/ssr/single-fetch.js +13 -22
- package/dist/development/lib/dom-export/dom-router-provider.js +1 -1
- package/dist/development/lib/dom-export/hydrated-router.js +2 -1
- package/dist/development/lib/errors.js +1 -1
- package/dist/development/lib/hooks.js +54 -46
- package/dist/development/lib/href.js +1 -1
- package/dist/development/lib/router/history.js +2 -2
- package/dist/development/lib/router/instrumentation.js +1 -1
- package/dist/development/lib/router/matcher-route-pattern.d.ts +2 -0
- package/dist/development/lib/router/matcher-route-pattern.js +216 -0
- package/dist/development/lib/router/matcher-route-pattern.preload.d.ts +20 -0
- package/dist/development/lib/router/matcher-route-pattern.preload.js +36 -0
- package/dist/development/lib/router/matcher.d.ts +2 -0
- package/dist/development/lib/router/matcher.js +29 -0
- package/dist/development/lib/router/navigation.js +45 -0
- package/dist/development/lib/router/router.d.ts +21 -7
- package/dist/development/lib/router/router.js +111 -82
- package/dist/development/lib/router/url.js +1 -1
- package/dist/development/lib/router/utils.d.ts +6 -1
- package/dist/development/lib/router/utils.js +15 -9
- package/dist/development/lib/rsc/browser.js +5 -1
- 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 +3 -5
- package/dist/development/lib/rsc/route-modules.js +1 -1
- package/dist/development/lib/rsc/server.rsc.d.ts +7 -0
- package/dist/development/lib/rsc/server.ssr.js +26 -22
- package/dist/development/lib/server-runtime/cookies.js +2 -2
- package/dist/development/lib/server-runtime/crypto.js +2 -2
- package/dist/development/lib/server-runtime/data.js +1 -1
- package/dist/development/lib/server-runtime/dev.js +2 -2
- 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 +3 -4
- package/dist/development/lib/server-runtime/routes.js +1 -1
- package/dist/development/lib/server-runtime/server.js +8 -7
- 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 +2 -2
- 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/route-pattern.d.ts +3 -0
- package/dist/development/route-pattern.js +12 -0
- package/dist/development/vendor/turbo-stream-v2/flatten.js +1 -1
- package/dist/development/vendor/turbo-stream-v2/turbo-stream.js +4 -4
- 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.js +1 -1
- package/dist/production/index-react-server-client.js +1 -1
- package/dist/production/index-react-server.d.ts +50 -48
- package/dist/production/index-react-server.js +96 -43
- package/dist/production/index.d.ts +2 -2
- package/dist/production/index.js +3 -3
- package/dist/production/lib/actions.js +10 -5
- package/dist/production/lib/components.js +50 -10
- package/dist/production/lib/context.d.ts +14 -5
- package/dist/production/lib/context.js +11 -3
- package/dist/production/lib/dom/dom.js +2 -2
- package/dist/production/lib/dom/lib.js +33 -36
- package/dist/production/lib/dom/server.d.ts +6 -1
- package/dist/production/lib/dom/server.js +40 -14
- package/dist/production/lib/dom/ssr/components.js +16 -24
- package/dist/production/lib/dom/ssr/data.js +1 -1
- package/dist/production/lib/dom/ssr/entry.d.ts +4 -0
- 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.js +34 -12
- package/dist/production/lib/dom/ssr/hydration.d.ts +3 -1
- package/dist/production/lib/dom/ssr/hydration.js +6 -4
- 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 +5 -2
- package/dist/production/lib/dom/ssr/routeModules.js +1 -1
- package/dist/production/lib/dom/ssr/routes-test-stub.js +1 -1
- package/dist/production/lib/dom/ssr/routes.js +1 -1
- package/dist/production/lib/dom/ssr/server.js +2 -2
- package/dist/production/lib/dom/ssr/single-fetch.js +13 -22
- package/dist/production/lib/dom-export/dom-router-provider.js +1 -1
- package/dist/production/lib/dom-export/hydrated-router.js +2 -1
- package/dist/production/lib/errors.js +1 -1
- package/dist/production/lib/hooks.js +54 -46
- package/dist/production/lib/href.js +1 -1
- package/dist/production/lib/router/history.js +2 -2
- package/dist/production/lib/router/instrumentation.js +1 -1
- package/dist/production/lib/router/matcher-route-pattern.d.ts +2 -0
- package/dist/production/lib/router/matcher-route-pattern.js +216 -0
- package/dist/production/lib/router/matcher-route-pattern.preload.d.ts +20 -0
- package/dist/production/lib/router/matcher-route-pattern.preload.js +36 -0
- package/dist/production/lib/router/matcher.d.ts +2 -0
- package/dist/production/lib/router/matcher.js +29 -0
- package/dist/production/lib/router/navigation.js +45 -0
- package/dist/production/lib/router/router.d.ts +21 -7
- package/dist/production/lib/router/router.js +111 -82
- package/dist/production/lib/router/url.js +1 -1
- package/dist/production/lib/router/utils.d.ts +6 -1
- package/dist/production/lib/router/utils.js +15 -9
- package/dist/production/lib/rsc/browser.js +5 -1
- 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 +3 -5
- package/dist/production/lib/rsc/route-modules.js +1 -1
- package/dist/production/lib/rsc/server.rsc.d.ts +7 -0
- package/dist/production/lib/rsc/server.ssr.js +26 -22
- package/dist/production/lib/server-runtime/cookies.js +2 -2
- package/dist/production/lib/server-runtime/crypto.js +2 -2
- package/dist/production/lib/server-runtime/data.js +1 -1
- package/dist/production/lib/server-runtime/dev.js +2 -2
- 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 +3 -4
- package/dist/production/lib/server-runtime/routes.js +1 -1
- package/dist/production/lib/server-runtime/server.js +8 -7
- 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 +2 -2
- 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/route-pattern.d.ts +3 -0
- package/dist/production/route-pattern.js +12 -0
- package/dist/production/vendor/turbo-stream-v2/flatten.js +1 -1
- package/dist/production/vendor/turbo-stream-v2/turbo-stream.js +4 -4
- package/dist/production/vendor/turbo-stream-v2/unflatten.js +1 -1
- package/dist/production/vendor/turbo-stream-v2/utils.js +1 -1
- package/docs/how-to/fetchers.md +6 -1
- package/docs/how-to/middleware.md +3 -1
- package/docs/how-to/optimize-revalidation.md +178 -5
- package/docs/how-to/react-server-components.md +26 -3
- package/docs/how-to/resource-routes.md +3 -0
- package/docs/start/data/actions.md +2 -1
- package/docs/start/data/route-object.md +21 -6
- package/docs/start/framework/actions.md +4 -2
- package/docs/start/framework/route-module.md +32 -5
- package/docs/upgrading/future.md +68 -0
- package/package.json +11 -1
|
@@ -358,7 +358,9 @@ In theory, we could have leveraged [`AsyncLocalStorage`][asynclocalstorage] dire
|
|
|
358
358
|
|
|
359
359
|
That said, this API still works great with React Router middleware and can be used in place of, or alongside of the `context` API:
|
|
360
360
|
|
|
361
|
-
<docs-info>[`AsyncLocalStorage`][asynclocalstorage] is _especially_ powerful when using [React Server Components](../how-to/react-server-components) because it allows you to provide information from `middleware` to your Server Components and
|
|
361
|
+
<docs-info>[`AsyncLocalStorage`][asynclocalstorage] is _especially_ powerful when using [React Server Components](../how-to/react-server-components) because it allows you to provide information from `middleware` to your Server Components and route actions because they run in the same server execution context 🤯</docs-info>
|
|
362
|
+
|
|
363
|
+
<docs-warning>Do not rely on route middleware to provide access control for React Server Functions. Server Functions are not inherently associated with a route, and a client can call the same Server Function through a URL with different middleware. Server Functions must perform all of their own access control checks. Use a route `action` instead when you want middleware-driven access control.</docs-warning>
|
|
362
364
|
|
|
363
365
|
```tsx filename=app/user-context.ts
|
|
364
366
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -1,12 +1,185 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Revalidation Optimization
|
|
3
|
-
hidden: true
|
|
4
3
|
---
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
# Revalidation Optimization
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
[MODES: framework, data]
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
<br/>
|
|
10
|
+
<br/>
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
After a mutation or some navigations, React Router re-runs loaders so
|
|
13
|
+
the UI stays in sync with the server. That default is the right
|
|
14
|
+
starting point. When a loader is expensive, or a mutation cannot
|
|
15
|
+
affect that route's data, you can skip the reload.
|
|
16
|
+
|
|
17
|
+
<docs-warning>
|
|
18
|
+
Skipping revalidation can leave the UI out of sync with the server.
|
|
19
|
+
Prefer targeting a specific action or navigation, and fall back to
|
|
20
|
+
`defaultShouldRevalidate` instead of always returning `false`.
|
|
21
|
+
</docs-warning>
|
|
22
|
+
|
|
23
|
+
## Default behavior
|
|
24
|
+
|
|
25
|
+
The default behavior differs between Framework and Data Modes:
|
|
26
|
+
|
|
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`](fetcher-submit))
|
|
29
|
+
- Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
|
|
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`])
|
|
32
|
+
- Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
|
|
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
|
+
- A GET navigation to the _exact_ same URL is treated like a page refresh and all loaders are revalidated.
|
|
35
|
+
|
|
36
|
+
Matched matched routes are handled independently - A child that skips
|
|
37
|
+
revalidation does not skip any ancestor routes.
|
|
38
|
+
|
|
39
|
+
[`fetcher.load`][use-fetcher] only revalidates by default after action
|
|
40
|
+
submissions and explicit [`useRevalidator`][use-revalidator] calls, not
|
|
41
|
+
on search-param or param-driven navigations.
|
|
42
|
+
|
|
43
|
+
A plain `fetch()` to a [resource route][resource-routes] does not
|
|
44
|
+
go through the router, so it does not revalidate loaders.
|
|
45
|
+
|
|
46
|
+
## Skip a route with `shouldRevalidate`
|
|
47
|
+
|
|
48
|
+
Export `shouldRevalidate` from the [route module][route-module]
|
|
49
|
+
(Framework Mode) or set it on the [route object][data-mode]
|
|
50
|
+
(Data Mode). Returning `false` skips **that route's** loader.
|
|
51
|
+
|
|
52
|
+
```tsx filename=app/routes/dashboard.tsx
|
|
53
|
+
// Framework Mode
|
|
54
|
+
export function shouldRevalidate() {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```tsx src/main.tsx
|
|
60
|
+
// Data Mode
|
|
61
|
+
createBrowserRouter([
|
|
62
|
+
{
|
|
63
|
+
path: "/dashboard",
|
|
64
|
+
loader: dashboardLoader,
|
|
65
|
+
shouldRevalidate: () => false,
|
|
66
|
+
Component: Dashboard,
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Always returning `false` opts that route out of the default
|
|
72
|
+
behavior completely, including cases you usually still want
|
|
73
|
+
(param changes, explicit [`useRevalidator`][use-revalidator]).
|
|
74
|
+
Prefer the conditional form below.
|
|
75
|
+
|
|
76
|
+
## Opt out of specific requests
|
|
77
|
+
|
|
78
|
+
Inspect
|
|
79
|
+
[`ShouldRevalidateFunctionArgs`][should-revalidate-args]
|
|
80
|
+
and return `defaultShouldRevalidate` for everything else.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import type { ShouldRevalidateFunctionArgs } from "react-router";
|
|
84
|
+
|
|
85
|
+
export function shouldRevalidate({
|
|
86
|
+
formMethod,
|
|
87
|
+
formAction,
|
|
88
|
+
defaultShouldRevalidate,
|
|
89
|
+
}: ShouldRevalidateFunctionArgs) {
|
|
90
|
+
if (
|
|
91
|
+
formMethod === "POST" &&
|
|
92
|
+
formAction?.endsWith("/analytics")
|
|
93
|
+
) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return defaultShouldRevalidate;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Other useful fields:
|
|
102
|
+
|
|
103
|
+
- `formData`, `json`, `text` — the submission body
|
|
104
|
+
- `actionResult`, `actionStatus` — the action's return value
|
|
105
|
+
- `currentUrl`, `nextUrl`, `currentParams`, `nextParams` —
|
|
106
|
+
the navigation
|
|
107
|
+
|
|
108
|
+
You can ignore search-param-only updates while still
|
|
109
|
+
revalidating when the pathname changes:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
export function shouldRevalidate({
|
|
113
|
+
currentUrl,
|
|
114
|
+
nextUrl,
|
|
115
|
+
defaultShouldRevalidate,
|
|
116
|
+
}: ShouldRevalidateFunctionArgs) {
|
|
117
|
+
if (currentUrl.pathname === nextUrl.pathname) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return defaultShouldRevalidate;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Skip revalidation for one event
|
|
126
|
+
|
|
127
|
+
Pass `defaultShouldRevalidate={false}` at the call site so you
|
|
128
|
+
do not have to change every route file. This works on
|
|
129
|
+
[`<Form>`][form], [`<Link>`][link], `<fetcher.Form>`, and as an
|
|
130
|
+
option to [`useSubmit`][use-submit], `fetcher.submit`,
|
|
131
|
+
[`useNavigate`][use-navigate], and
|
|
132
|
+
[`useSearchParams`][use-search-params].
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { Form, Link } from "react-router";
|
|
136
|
+
|
|
137
|
+
<Link
|
|
138
|
+
to="/search?q=shoes"
|
|
139
|
+
defaultShouldRevalidate={false}
|
|
140
|
+
>
|
|
141
|
+
Search Shoes
|
|
142
|
+
</Link>
|
|
143
|
+
|
|
144
|
+
<Form
|
|
145
|
+
method="post"
|
|
146
|
+
action="/analytics"
|
|
147
|
+
defaultShouldRevalidate={false}
|
|
148
|
+
>
|
|
149
|
+
<button>Track Click</button>
|
|
150
|
+
</Form>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
fetcher.submit(
|
|
155
|
+
{ intent: "save-progress" },
|
|
156
|
+
{
|
|
157
|
+
method: "post",
|
|
158
|
+
action: "/save-progress",
|
|
159
|
+
defaultShouldRevalidate: false,
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
If a matched route does **not** export `shouldRevalidate`, this
|
|
165
|
+
value is used directly for that loader. If it **does** export
|
|
166
|
+
`shouldRevalidate`, the value is passed in as
|
|
167
|
+
`defaultShouldRevalidate` and the route still has the final say.
|
|
168
|
+
|
|
169
|
+
That is why a child `shouldRevalidate` that always returns
|
|
170
|
+
`false` cannot hide a root reload after `fetcher.submit`. Either
|
|
171
|
+
also opt `root` out for that case, or pass
|
|
172
|
+
`defaultShouldRevalidate: false` at the call site when `root`
|
|
173
|
+
has no `shouldRevalidate` of its own.
|
|
174
|
+
|
|
175
|
+
[data-mode]: ../start/data/route-object#shouldrevalidate
|
|
176
|
+
[form]: ../api/components/Form
|
|
177
|
+
[link]: ../api/components/Link
|
|
178
|
+
[resource-routes]: ./resource-routes
|
|
179
|
+
[route-module]: ../start/framework/route-module#shouldrevalidate
|
|
180
|
+
[should-revalidate-args]: https://api.reactrouter.com/v8/interfaces/react-router.ShouldRevalidateFunctionArgs.html
|
|
181
|
+
[use-fetcher]: ../api/hooks/useFetcher
|
|
182
|
+
[use-navigate]: ../api/hooks/useNavigate
|
|
183
|
+
[use-revalidator]: ../api/hooks/useRevalidator
|
|
184
|
+
[use-search-params]: ../api/hooks/useSearchParams
|
|
185
|
+
[use-submit]: ../api/hooks/useSubmit
|
|
@@ -498,16 +498,39 @@ Using Server Components in loaders can be helpful for incremental adoption of RS
|
|
|
498
498
|
|
|
499
499
|
[Server Functions][react-server-functions-doc] are a React feature that allow you to call async functions executed on the server. They're defined with the [`"use server"`][use-server-docs] directive.
|
|
500
500
|
|
|
501
|
+
<docs-warning>
|
|
502
|
+
|
|
503
|
+
Treat every Server Function as a public endpoint. The client controls both the
|
|
504
|
+
Server Function identifier and request URL, so do not rely on route middleware
|
|
505
|
+
for authentication or authorization. Server Functions must perform their own
|
|
506
|
+
access control and input validation; use a route `action` when access control
|
|
507
|
+
should be middleware-driven.
|
|
508
|
+
|
|
509
|
+
</docs-warning>
|
|
510
|
+
|
|
501
511
|
```tsx
|
|
502
512
|
"use server";
|
|
503
513
|
|
|
514
|
+
import { unstable_getRequest as getRequest } from "react-router";
|
|
515
|
+
import { requireUser } from "./auth.ts";
|
|
516
|
+
|
|
504
517
|
export async function updateFavorite(formData: FormData) {
|
|
505
|
-
let
|
|
518
|
+
let user = await requireUser(getRequest());
|
|
519
|
+
let movieId = Number(formData.get("id"));
|
|
506
520
|
let intent = formData.get("intent");
|
|
521
|
+
|
|
522
|
+
if (
|
|
523
|
+
!Number.isSafeInteger(movieId) ||
|
|
524
|
+
movieId <= 0 ||
|
|
525
|
+
(intent !== "add" && intent !== "remove")
|
|
526
|
+
) {
|
|
527
|
+
throw new Error("Invalid form submission");
|
|
528
|
+
}
|
|
529
|
+
|
|
507
530
|
if (intent === "add") {
|
|
508
|
-
await addFavorite(
|
|
531
|
+
await addFavorite(user.id, movieId);
|
|
509
532
|
} else {
|
|
510
|
-
await removeFavorite(
|
|
533
|
+
await removeFavorite(user.id, movieId);
|
|
511
534
|
}
|
|
512
535
|
}
|
|
513
536
|
```
|
|
@@ -66,6 +66,8 @@ export function action(_: Route.ActionArgs) {
|
|
|
66
66
|
}
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
Calling this `action` through [`<Form>`][form] or [`useFetcher`][fetcher] still revalidates matched UI loaders. A plain `fetch()` to the resource URL does not. See [Revalidation Optimization][optimize-revalidation] for more info.
|
|
70
|
+
|
|
69
71
|
## Return Types
|
|
70
72
|
|
|
71
73
|
Resource Routes are flexible when it comes to the return type - you can return [`Response`][Response] instances or [`data()`][data] objects. A good general rule of thumb when deciding which type to use is:
|
|
@@ -124,3 +126,4 @@ export function action() {
|
|
|
124
126
|
[form]: ../api/components/Form
|
|
125
127
|
[await]: ../api/components/Await
|
|
126
128
|
[error-boundary]: ../start/framework/route-module#errorboundary
|
|
129
|
+
[optimize-revalidation]: ./optimize-revalidation
|
|
@@ -9,7 +9,7 @@ order: 5
|
|
|
9
9
|
|
|
10
10
|
## Defining Actions
|
|
11
11
|
|
|
12
|
-
Data mutations are done through Route actions defined on the `action` property of a route object. When the action completes, all loader data on the page is revalidated to keep your UI in sync with the data without writing any code to do it.
|
|
12
|
+
Data mutations are done through Route actions defined on the `action` property of a route object. When the action completes, all loader data on the page is revalidated to keep your UI in sync with the data without writing any code to do it. To skip some or all of those reloads, see [Revalidation Optimization][optimize-revalidation].
|
|
13
13
|
|
|
14
14
|
```tsx
|
|
15
15
|
import { createBrowserRouter } from "react-router";
|
|
@@ -136,3 +136,4 @@ function Project() {
|
|
|
136
136
|
Next: [Navigating](./navigating)
|
|
137
137
|
|
|
138
138
|
[fetchers]: ../../how-to/fetchers
|
|
139
|
+
[optimize-revalidation]: ../../how-to/optimize-revalidation
|
|
@@ -188,7 +188,7 @@ export default function Items() {
|
|
|
188
188
|
|
|
189
189
|
Loader data is automatically revalidated after certain events like navigations and form submissions.
|
|
190
190
|
|
|
191
|
-
This
|
|
191
|
+
This function lets you opt in or out of the default revalidation behavior **for this route's loader**. It does not skip parent or sibling loaders. The default behavior is nuanced to avoid calling loaders unnecessarily.
|
|
192
192
|
|
|
193
193
|
A route loader is revalidated when:
|
|
194
194
|
|
|
@@ -201,21 +201,34 @@ By defining this function, you opt out of the default behavior completely and ca
|
|
|
201
201
|
```tsx
|
|
202
202
|
import type { ShouldRevalidateFunctionArgs } from "react-router";
|
|
203
203
|
|
|
204
|
-
function shouldRevalidate(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
function shouldRevalidate({
|
|
205
|
+
formMethod,
|
|
206
|
+
formAction,
|
|
207
|
+
defaultShouldRevalidate,
|
|
208
|
+
}: ShouldRevalidateFunctionArgs) {
|
|
209
|
+
if (
|
|
210
|
+
formMethod === "POST" &&
|
|
211
|
+
formAction?.endsWith("/analytics")
|
|
212
|
+
) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return defaultShouldRevalidate;
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
createBrowserRouter([
|
|
211
220
|
{
|
|
212
221
|
path: "/",
|
|
213
|
-
shouldRevalidate
|
|
222
|
+
shouldRevalidate,
|
|
214
223
|
Component: MyRoute,
|
|
215
224
|
},
|
|
216
225
|
]);
|
|
217
226
|
```
|
|
218
227
|
|
|
228
|
+
To skip revalidation for a single `<Form>`, `<Link>`, `useSubmit`, or `fetcher.submit`, pass [`defaultShouldRevalidate={false}`][form-default-should-revalidate] at the call site. Routes without `shouldRevalidate` use that value directly.
|
|
229
|
+
|
|
230
|
+
See [Revalidation Optimization][optimize-revalidation] for call-site opt-out, parent/child behavior, and more examples.
|
|
231
|
+
|
|
219
232
|
[`ShouldRevalidateFunctionArgs` Reference Documentation ↗](https://api.reactrouter.com/v8/interfaces/react-router.ShouldRevalidateFunctionArgs.html)
|
|
220
233
|
|
|
221
234
|
Please note the default behavior is different in [Framework Mode](../modes).
|
|
@@ -263,6 +276,8 @@ See also:
|
|
|
263
276
|
|
|
264
277
|
Next: [Data Loading](./data-loading)
|
|
265
278
|
|
|
279
|
+
[form-default-should-revalidate]: ../../api/components/Form#defaultshouldrevalidate
|
|
266
280
|
[loader-params]: https://api.reactrouter.com/v8/interfaces/react-router.LoaderFunctionArgs
|
|
267
281
|
[middleware]: ../../how-to/middleware
|
|
282
|
+
[optimize-revalidation]: ../../how-to/optimize-revalidation
|
|
268
283
|
[use-matches]: ../../api/hooks/useMatches
|
|
@@ -9,7 +9,7 @@ order: 6
|
|
|
9
9
|
|
|
10
10
|
## Introduction
|
|
11
11
|
|
|
12
|
-
Data mutations are done through Route actions. When the action completes, all loader data on the page is revalidated to keep your UI in sync with the data without writing any code to do it.
|
|
12
|
+
Data mutations are done through Route actions. When the action completes, all loader data on the page is revalidated to keep your UI in sync with the data without writing any code to do it. To skip some or all of those reloads, see [Revalidation Optimization][optimize-revalidation].
|
|
13
13
|
|
|
14
14
|
Route actions defined with `action` are only called on the server while actions defined with `clientAction` are run in the browser.
|
|
15
15
|
|
|
@@ -165,10 +165,12 @@ fetcher.submit(
|
|
|
165
165
|
);
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
See the [Using Fetchers][fetchers] guide for more information.
|
|
168
|
+
`fetcher.submit` takes [`FetcherSubmitOptions`][fetcher-submit-options], including `defaultShouldRevalidate`. See the [Using Fetchers][fetchers] guide for more information.
|
|
169
169
|
|
|
170
170
|
---
|
|
171
171
|
|
|
172
172
|
Next: [Navigating](./navigating)
|
|
173
173
|
|
|
174
174
|
[fetchers]: ../../how-to/fetchers
|
|
175
|
+
[fetcher-submit-options]: https://api.reactrouter.com/v8/interfaces/react-router.FetcherSubmitOptions.html#defaultshouldrevalidate
|
|
176
|
+
[optimize-revalidation]: ../../how-to/optimize-revalidation
|
|
@@ -486,18 +486,43 @@ The meta of the last matching route is used, allowing you to override parent rou
|
|
|
486
486
|
|
|
487
487
|
In framework mode with SSR, route loaders are automatically revalidated after all navigations and form submissions (this is different from [Data Mode][data-mode-should-revalidate]). This enables middleware and loaders to share a request context and optimize in different ways than they would in Data Mode.
|
|
488
488
|
|
|
489
|
-
Defining this function allows you to opt out of revalidation for
|
|
489
|
+
Defining this function allows you to opt out of revalidation for **this route's** loader. It does not skip parent or sibling loaders. After `fetcher.submit()`, `root` still revalidates unless it also opts out.
|
|
490
|
+
|
|
491
|
+
Returning `false` for every call turns revalidation off for this loader:
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
export function shouldRevalidate() {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
<docs-warning>
|
|
500
|
+
Always returning `false` can leave this route's UI out of sync with the server. Prefer inspecting the arguments and falling back to `defaultShouldRevalidate`.
|
|
501
|
+
</docs-warning>
|
|
490
502
|
|
|
491
503
|
```tsx
|
|
492
504
|
import type { ShouldRevalidateFunctionArgs } from "react-router";
|
|
493
505
|
|
|
494
|
-
export function shouldRevalidate(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
506
|
+
export function shouldRevalidate({
|
|
507
|
+
formMethod,
|
|
508
|
+
formAction,
|
|
509
|
+
defaultShouldRevalidate,
|
|
510
|
+
}: ShouldRevalidateFunctionArgs) {
|
|
511
|
+
if (
|
|
512
|
+
formMethod === "POST" &&
|
|
513
|
+
formAction?.endsWith("/analytics")
|
|
514
|
+
) {
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return defaultShouldRevalidate;
|
|
498
519
|
}
|
|
499
520
|
```
|
|
500
521
|
|
|
522
|
+
To skip revalidation for a single `<Form>`, `<Link>`, `useSubmit`, or `fetcher.submit` without changing every route file, pass [`defaultShouldRevalidate={false}`][form-default-should-revalidate] at the call site. Routes without a `shouldRevalidate` export use that value directly; routes that export one receive it as `defaultShouldRevalidate` and still decide.
|
|
523
|
+
|
|
524
|
+
See [Revalidation Optimization][optimize-revalidation] for Framework vs Data Mode defaults, parent/child behavior, and more examples.
|
|
525
|
+
|
|
501
526
|
When using [SPA Mode][spa-mode], there are no server loaders to call on navigations, so `shouldRevalidate` behaves the same as it does in [Data Mode][data-mode-should-revalidate].
|
|
502
527
|
|
|
503
528
|
[`ShouldRevalidateFunctionArgs` Reference Documentation ↗](https://api.reactrouter.com/v8/interfaces/react-router.ShouldRevalidateFunctionArgs.html)
|
|
@@ -522,6 +547,8 @@ Next: [Rendering Strategies](./rendering)
|
|
|
522
547
|
[meta-params]: https://api.reactrouter.com/v8/interfaces/react-router.MetaArgs
|
|
523
548
|
[meta-function]: https://api.reactrouter.com/v8/types/react-router.MetaDescriptor.html
|
|
524
549
|
[data-mode-should-revalidate]: ../data/route-object#shouldrevalidate
|
|
550
|
+
[form-default-should-revalidate]: ../../api/components/Form#defaultshouldrevalidate
|
|
551
|
+
[optimize-revalidation]: ../../how-to/optimize-revalidation
|
|
525
552
|
[spa-mode]: ../../how-to/spa
|
|
526
553
|
[client-data]: ../../how-to/client-data
|
|
527
554
|
[styling]: ../../explanation/styling
|
package/docs/upgrading/future.md
CHANGED
|
@@ -106,6 +106,74 @@ export default {
|
|
|
106
106
|
|
|
107
107
|
No code changes are required. If you run into dependency optimization issues after enabling this flag, remove the flag and restart the dev server.
|
|
108
108
|
|
|
109
|
+
### `future.unstable_routePatternMatching`
|
|
110
|
+
|
|
111
|
+
[MODES: data]
|
|
112
|
+
|
|
113
|
+
<br/>
|
|
114
|
+
<br/>
|
|
115
|
+
|
|
116
|
+
**Background**
|
|
117
|
+
|
|
118
|
+
This flag opts Data Routers into a new (and vastly more efficient) route matcher
|
|
119
|
+
powered by [`@remix-run/route-pattern`](https://github.com/remix-run/remix/tree/main/packages/route-pattern).
|
|
120
|
+
It supports the existing React Router path syntax and matching behavior, but may
|
|
121
|
+
rank _slightly_ differently in some cases - please read the section below on
|
|
122
|
+
potential ranking differences.
|
|
123
|
+
|
|
124
|
+
👉 **Preload the Matcher and Enable the Flag**
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { createBrowserRouter } from "react-router";
|
|
128
|
+
import { unstable_preloadRoutePattern } from "react-router/route-pattern";
|
|
129
|
+
|
|
130
|
+
unstable_preloadRoutePattern();
|
|
131
|
+
|
|
132
|
+
const router = createBrowserRouter(routes, {
|
|
133
|
+
future: {
|
|
134
|
+
unstable_routePatternMatching: true,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The `react-router/route-pattern` sub-export statically imports the new matcher
|
|
140
|
+
implementation. Tree-shaking bundlers remove it from applications that do not
|
|
141
|
+
use the preload function. You must call the function before creating a router
|
|
142
|
+
with the flag enabled - router creation will throw if the matcher has not been
|
|
143
|
+
initialized. Initialization is synchronous, and repeated calls are safe.
|
|
144
|
+
|
|
145
|
+
**Update your Code**
|
|
146
|
+
|
|
147
|
+
No route configuration changes are required, but you should review any routes with
|
|
148
|
+
overlapping patterns to ensure the new ranking behavior selects the intended route.
|
|
149
|
+
The new implementation matches by positional specificity instead of aggregate
|
|
150
|
+
segment scores. This means a route with a longer static prefix can rank above a
|
|
151
|
+
route with more dynamic segments.
|
|
152
|
+
|
|
153
|
+
For example, both of these routes match `/products/one/two/three`:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const routes = [
|
|
157
|
+
{ path: "/products/*", id: "products" },
|
|
158
|
+
{
|
|
159
|
+
path: "/:first/:second/:third/:fourth",
|
|
160
|
+
id: "segments",
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The legacy matcher selects `segments` based on its aggregate segment score. The
|
|
166
|
+
new matcher selects `products` because its static `products` segment is more
|
|
167
|
+
specific than the dynamic `:first` segment in the same position.
|
|
168
|
+
|
|
169
|
+
Once you enable this flag, use the `router.match()` when you need to match a
|
|
170
|
+
location (this is currently marked private and will become stable at the same
|
|
171
|
+
time this flag stabilizes). Standalone matching APIs such as `matchRoutes`,
|
|
172
|
+
`matchPath`, and `useMatch` continue to use the legacy matcher and may return
|
|
173
|
+
different matches than the router.
|
|
174
|
+
|
|
175
|
+
Case-sensitive routes are not currently supported with this flag.
|
|
176
|
+
|
|
109
177
|
[api-development-strategy]: ../community/api-development-strategy
|
|
110
178
|
[governance]: https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#design-goals
|
|
111
179
|
[unstable]: ../community/api-development-strategy#unstable-flags
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-router",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.4.0",
|
|
5
5
|
"description": "Declarative routing for React",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"react",
|
|
@@ -46,6 +46,15 @@
|
|
|
46
46
|
"development": "./dist/development/dom-export.js",
|
|
47
47
|
"default": "./dist/production/dom-export.js"
|
|
48
48
|
},
|
|
49
|
+
"./route-pattern": {
|
|
50
|
+
"types": "./dist/production/route-pattern.d.ts",
|
|
51
|
+
"module-sync": {
|
|
52
|
+
"development": "./dist/development/route-pattern.js",
|
|
53
|
+
"default": "./dist/production/route-pattern.js"
|
|
54
|
+
},
|
|
55
|
+
"development": "./dist/development/route-pattern.js",
|
|
56
|
+
"default": "./dist/production/route-pattern.js"
|
|
57
|
+
},
|
|
49
58
|
"./internal": {
|
|
50
59
|
"types": "./dist/development/lib/types/internal.d.ts"
|
|
51
60
|
},
|
|
@@ -76,6 +85,7 @@
|
|
|
76
85
|
}
|
|
77
86
|
},
|
|
78
87
|
"dependencies": {
|
|
88
|
+
"@remix-run/route-pattern": "^0.22.1",
|
|
79
89
|
"cookie-es": "^3.1.1"
|
|
80
90
|
},
|
|
81
91
|
"devDependencies": {
|