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.
Files changed (176) hide show
  1. package/CHANGELOG.md +46 -2
  2. package/dist/development/dom-export.js +1 -1
  3. package/dist/development/index-react-server-client.js +1 -1
  4. package/dist/development/index-react-server.d.ts +50 -48
  5. package/dist/development/index-react-server.js +96 -43
  6. package/dist/development/index.d.ts +2 -2
  7. package/dist/development/index.js +3 -3
  8. package/dist/development/lib/actions.js +10 -5
  9. package/dist/development/lib/components.js +50 -10
  10. package/dist/development/lib/context.d.ts +14 -5
  11. package/dist/development/lib/context.js +11 -3
  12. package/dist/development/lib/dom/dom.js +2 -2
  13. package/dist/development/lib/dom/lib.js +33 -36
  14. package/dist/development/lib/dom/server.d.ts +6 -1
  15. package/dist/development/lib/dom/server.js +40 -14
  16. package/dist/development/lib/dom/ssr/components.js +16 -24
  17. package/dist/development/lib/dom/ssr/data.js +1 -1
  18. package/dist/development/lib/dom/ssr/entry.d.ts +4 -0
  19. package/dist/development/lib/dom/ssr/errorBoundaries.js +1 -1
  20. package/dist/development/lib/dom/ssr/fallback.js +1 -1
  21. package/dist/development/lib/dom/ssr/fog-of-war.js +34 -12
  22. package/dist/development/lib/dom/ssr/hydration.d.ts +3 -1
  23. package/dist/development/lib/dom/ssr/hydration.js +6 -4
  24. package/dist/development/lib/dom/ssr/invariant.js +1 -1
  25. package/dist/development/lib/dom/ssr/links.js +1 -1
  26. package/dist/development/lib/dom/ssr/markup.js +5 -2
  27. package/dist/development/lib/dom/ssr/routeModules.js +1 -1
  28. package/dist/development/lib/dom/ssr/routes-test-stub.js +1 -1
  29. package/dist/development/lib/dom/ssr/routes.js +1 -1
  30. package/dist/development/lib/dom/ssr/server.js +2 -2
  31. package/dist/development/lib/dom/ssr/single-fetch.js +13 -22
  32. package/dist/development/lib/dom-export/dom-router-provider.js +1 -1
  33. package/dist/development/lib/dom-export/hydrated-router.js +2 -1
  34. package/dist/development/lib/errors.js +1 -1
  35. package/dist/development/lib/hooks.js +54 -46
  36. package/dist/development/lib/href.js +1 -1
  37. package/dist/development/lib/router/history.js +2 -2
  38. package/dist/development/lib/router/instrumentation.js +1 -1
  39. package/dist/development/lib/router/matcher-route-pattern.d.ts +2 -0
  40. package/dist/development/lib/router/matcher-route-pattern.js +216 -0
  41. package/dist/development/lib/router/matcher-route-pattern.preload.d.ts +20 -0
  42. package/dist/development/lib/router/matcher-route-pattern.preload.js +36 -0
  43. package/dist/development/lib/router/matcher.d.ts +2 -0
  44. package/dist/development/lib/router/matcher.js +29 -0
  45. package/dist/development/lib/router/navigation.js +45 -0
  46. package/dist/development/lib/router/router.d.ts +21 -7
  47. package/dist/development/lib/router/router.js +111 -82
  48. package/dist/development/lib/router/url.js +1 -1
  49. package/dist/development/lib/router/utils.d.ts +6 -1
  50. package/dist/development/lib/router/utils.js +15 -9
  51. package/dist/development/lib/rsc/browser.js +5 -1
  52. package/dist/development/lib/rsc/errorBoundaries.js +1 -1
  53. package/dist/development/lib/rsc/html-stream/browser.js +1 -1
  54. package/dist/development/lib/rsc/html-stream/server.js +3 -5
  55. package/dist/development/lib/rsc/route-modules.js +1 -1
  56. package/dist/development/lib/rsc/server.rsc.d.ts +7 -0
  57. package/dist/development/lib/rsc/server.ssr.js +26 -22
  58. package/dist/development/lib/server-runtime/cookies.js +2 -2
  59. package/dist/development/lib/server-runtime/crypto.js +2 -2
  60. package/dist/development/lib/server-runtime/data.js +1 -1
  61. package/dist/development/lib/server-runtime/dev.js +2 -2
  62. package/dist/development/lib/server-runtime/entry.js +1 -1
  63. package/dist/development/lib/server-runtime/errors.js +1 -1
  64. package/dist/development/lib/server-runtime/headers.js +1 -1
  65. package/dist/development/lib/server-runtime/invariant.js +1 -1
  66. package/dist/development/lib/server-runtime/mode.js +1 -1
  67. package/dist/development/lib/server-runtime/routeMatching.js +3 -4
  68. package/dist/development/lib/server-runtime/routes.js +1 -1
  69. package/dist/development/lib/server-runtime/server.js +8 -7
  70. package/dist/development/lib/server-runtime/serverHandoff.js +1 -1
  71. package/dist/development/lib/server-runtime/sessions/cookieStorage.js +1 -1
  72. package/dist/development/lib/server-runtime/sessions/memoryStorage.js +1 -1
  73. package/dist/development/lib/server-runtime/sessions.js +1 -1
  74. package/dist/development/lib/server-runtime/single-fetch.js +2 -2
  75. package/dist/development/lib/server-runtime/urls.js +1 -1
  76. package/dist/development/lib/server-runtime/warnings.js +1 -1
  77. package/dist/development/lib/types/internal.js +1 -1
  78. package/dist/development/route-pattern.d.ts +3 -0
  79. package/dist/development/route-pattern.js +12 -0
  80. package/dist/development/vendor/turbo-stream-v2/flatten.js +1 -1
  81. package/dist/development/vendor/turbo-stream-v2/turbo-stream.js +4 -4
  82. package/dist/development/vendor/turbo-stream-v2/unflatten.js +1 -1
  83. package/dist/development/vendor/turbo-stream-v2/utils.js +1 -1
  84. package/dist/production/dom-export.js +1 -1
  85. package/dist/production/index-react-server-client.js +1 -1
  86. package/dist/production/index-react-server.d.ts +50 -48
  87. package/dist/production/index-react-server.js +96 -43
  88. package/dist/production/index.d.ts +2 -2
  89. package/dist/production/index.js +3 -3
  90. package/dist/production/lib/actions.js +10 -5
  91. package/dist/production/lib/components.js +50 -10
  92. package/dist/production/lib/context.d.ts +14 -5
  93. package/dist/production/lib/context.js +11 -3
  94. package/dist/production/lib/dom/dom.js +2 -2
  95. package/dist/production/lib/dom/lib.js +33 -36
  96. package/dist/production/lib/dom/server.d.ts +6 -1
  97. package/dist/production/lib/dom/server.js +40 -14
  98. package/dist/production/lib/dom/ssr/components.js +16 -24
  99. package/dist/production/lib/dom/ssr/data.js +1 -1
  100. package/dist/production/lib/dom/ssr/entry.d.ts +4 -0
  101. package/dist/production/lib/dom/ssr/errorBoundaries.js +1 -1
  102. package/dist/production/lib/dom/ssr/fallback.js +1 -1
  103. package/dist/production/lib/dom/ssr/fog-of-war.js +34 -12
  104. package/dist/production/lib/dom/ssr/hydration.d.ts +3 -1
  105. package/dist/production/lib/dom/ssr/hydration.js +6 -4
  106. package/dist/production/lib/dom/ssr/invariant.js +1 -1
  107. package/dist/production/lib/dom/ssr/links.js +1 -1
  108. package/dist/production/lib/dom/ssr/markup.js +5 -2
  109. package/dist/production/lib/dom/ssr/routeModules.js +1 -1
  110. package/dist/production/lib/dom/ssr/routes-test-stub.js +1 -1
  111. package/dist/production/lib/dom/ssr/routes.js +1 -1
  112. package/dist/production/lib/dom/ssr/server.js +2 -2
  113. package/dist/production/lib/dom/ssr/single-fetch.js +13 -22
  114. package/dist/production/lib/dom-export/dom-router-provider.js +1 -1
  115. package/dist/production/lib/dom-export/hydrated-router.js +2 -1
  116. package/dist/production/lib/errors.js +1 -1
  117. package/dist/production/lib/hooks.js +54 -46
  118. package/dist/production/lib/href.js +1 -1
  119. package/dist/production/lib/router/history.js +2 -2
  120. package/dist/production/lib/router/instrumentation.js +1 -1
  121. package/dist/production/lib/router/matcher-route-pattern.d.ts +2 -0
  122. package/dist/production/lib/router/matcher-route-pattern.js +216 -0
  123. package/dist/production/lib/router/matcher-route-pattern.preload.d.ts +20 -0
  124. package/dist/production/lib/router/matcher-route-pattern.preload.js +36 -0
  125. package/dist/production/lib/router/matcher.d.ts +2 -0
  126. package/dist/production/lib/router/matcher.js +29 -0
  127. package/dist/production/lib/router/navigation.js +45 -0
  128. package/dist/production/lib/router/router.d.ts +21 -7
  129. package/dist/production/lib/router/router.js +111 -82
  130. package/dist/production/lib/router/url.js +1 -1
  131. package/dist/production/lib/router/utils.d.ts +6 -1
  132. package/dist/production/lib/router/utils.js +15 -9
  133. package/dist/production/lib/rsc/browser.js +5 -1
  134. package/dist/production/lib/rsc/errorBoundaries.js +1 -1
  135. package/dist/production/lib/rsc/html-stream/browser.js +1 -1
  136. package/dist/production/lib/rsc/html-stream/server.js +3 -5
  137. package/dist/production/lib/rsc/route-modules.js +1 -1
  138. package/dist/production/lib/rsc/server.rsc.d.ts +7 -0
  139. package/dist/production/lib/rsc/server.ssr.js +26 -22
  140. package/dist/production/lib/server-runtime/cookies.js +2 -2
  141. package/dist/production/lib/server-runtime/crypto.js +2 -2
  142. package/dist/production/lib/server-runtime/data.js +1 -1
  143. package/dist/production/lib/server-runtime/dev.js +2 -2
  144. package/dist/production/lib/server-runtime/entry.js +1 -1
  145. package/dist/production/lib/server-runtime/errors.js +1 -1
  146. package/dist/production/lib/server-runtime/headers.js +1 -1
  147. package/dist/production/lib/server-runtime/invariant.js +1 -1
  148. package/dist/production/lib/server-runtime/mode.js +1 -1
  149. package/dist/production/lib/server-runtime/routeMatching.js +3 -4
  150. package/dist/production/lib/server-runtime/routes.js +1 -1
  151. package/dist/production/lib/server-runtime/server.js +8 -7
  152. package/dist/production/lib/server-runtime/serverHandoff.js +1 -1
  153. package/dist/production/lib/server-runtime/sessions/cookieStorage.js +1 -1
  154. package/dist/production/lib/server-runtime/sessions/memoryStorage.js +1 -1
  155. package/dist/production/lib/server-runtime/sessions.js +1 -1
  156. package/dist/production/lib/server-runtime/single-fetch.js +2 -2
  157. package/dist/production/lib/server-runtime/urls.js +1 -1
  158. package/dist/production/lib/server-runtime/warnings.js +1 -1
  159. package/dist/production/lib/types/internal.js +1 -1
  160. package/dist/production/route-pattern.d.ts +3 -0
  161. package/dist/production/route-pattern.js +12 -0
  162. package/dist/production/vendor/turbo-stream-v2/flatten.js +1 -1
  163. package/dist/production/vendor/turbo-stream-v2/turbo-stream.js +4 -4
  164. package/dist/production/vendor/turbo-stream-v2/unflatten.js +1 -1
  165. package/dist/production/vendor/turbo-stream-v2/utils.js +1 -1
  166. package/docs/how-to/fetchers.md +6 -1
  167. package/docs/how-to/middleware.md +3 -1
  168. package/docs/how-to/optimize-revalidation.md +178 -5
  169. package/docs/how-to/react-server-components.md +26 -3
  170. package/docs/how-to/resource-routes.md +3 -0
  171. package/docs/start/data/actions.md +2 -1
  172. package/docs/start/data/route-object.md +21 -6
  173. package/docs/start/framework/actions.md +4 -2
  174. package/docs/start/framework/route-module.md +32 -5
  175. package/docs/upgrading/future.md +68 -0
  176. 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 Server Actions because they run in the same server execution context 🤯</docs-info>
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
- [copy pasted]
5
+ # Revalidation Optimization
7
6
 
8
- During client-side transitions, React Router will optimize reloading of routes that are already rendering, like not reloading layout routes that aren't changing. In other cases, like form submissions or search param changes, React Router doesn't know which routes need to be reloaded, so it reloads them all to be safe. This ensures your UI always stays in sync with the state on your server.
7
+ [MODES: framework, data]
9
8
 
10
- This function lets apps further optimize by returning `false` when React Router is about to reload a route. If you define this function on a route module, React Router will defer to your function on every navigation and every revalidation after an action is called. Again, this makes it possible for your UI to get out of sync with your server if you do it wrong, so be careful.
9
+ <br/>
10
+ <br/>
11
11
 
12
- `fetcher.load` calls also revalidate, but because they load a specific URL, they don't have to worry about route param or URL search param revalidations. `fetcher.load`'s only revalidate by default after action submissions and explicit revalidation requests via [`useRevalidator`][use-revalidator].
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 movieId = formData.get("id");
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(Number(movieId));
531
+ await addFavorite(user.id, movieId);
509
532
  } else {
510
- await removeFavorite(Number(movieId));
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 hook enables you to opt in or out of the default revalidation behavior. The default behavior is nuanced to avoid calling loaders unnecessarily.
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
- arg: ShouldRevalidateFunctionArgs,
206
- ) {
207
- return true; // false
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: 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 a route loader for navigations and form submissions.
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
- arg: ShouldRevalidateFunctionArgs,
496
- ) {
497
- return true;
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
@@ -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.3.0",
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": {