react-router 7.16.0 → 7.17.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 +9 -1
- package/dist/development/{browser-nIQ4Nsyi.d.mts → browser-CGcs-0pD.d.mts} +1 -1
- package/dist/development/{chunk-QUQL4437.mjs → chunk-6CSD65Y2.mjs} +2 -2
- package/dist/{production/chunk-NALGHHKE.mjs → development/chunk-ASILSGTR.mjs} +2 -2
- package/dist/development/{chunk-SRID2YZ2.js → chunk-KFNXW4AL.js} +1 -1
- package/dist/development/{chunk-XEJDWL2B.js → chunk-PBLBZ3QU.js} +7 -7
- package/dist/{production/chunk-SKEDDLRM.js → development/chunk-PULC7NLK.js} +99 -99
- package/dist/development/{context-m8rizgnE.d.mts → context-CmHpk1Ws.d.mts} +1 -1
- package/dist/development/dom-export.d.mts +3 -3
- package/dist/development/dom-export.d.ts +1 -1
- package/dist/development/dom-export.js +28 -28
- package/dist/development/dom-export.mjs +3 -3
- package/dist/development/{index-react-server-client-BLiUx67a.d.ts → index-react-server-client-CwU9bE5R.d.ts} +1 -1
- package/dist/development/{index-react-server-client-CdKROblb.d.mts → index-react-server-client-DPrDrCew.d.mts} +1 -1
- package/dist/development/index-react-server-client.d.mts +2 -2
- package/dist/development/index-react-server-client.d.ts +1 -1
- package/dist/development/index-react-server-client.js +4 -4
- package/dist/development/index-react-server-client.mjs +2 -2
- package/dist/development/index-react-server.js +1 -1
- package/dist/development/index-react-server.mjs +1 -1
- package/dist/development/index.d.mts +6 -6
- package/dist/development/index.d.ts +2 -2
- package/dist/development/index.js +85 -85
- package/dist/development/index.mjs +3 -3
- package/dist/development/lib/types/internal.js +1 -1
- package/dist/development/lib/types/internal.mjs +1 -1
- package/dist/production/{browser-nIQ4Nsyi.d.mts → browser-CGcs-0pD.d.mts} +1 -1
- package/dist/{development/chunk-S54KXAEJ.mjs → production/chunk-5TQZEVD5.mjs} +2 -2
- package/dist/production/{chunk-EAQNHM3N.js → chunk-CTIXC7EV.js} +7 -7
- package/dist/{development/chunk-IBI7OMNB.js → production/chunk-EN242BO4.js} +99 -99
- package/dist/production/{chunk-Q65P7S7Y.mjs → chunk-OSYEOCBT.mjs} +2 -2
- package/dist/production/{chunk-Y7DNFQZP.js → chunk-RTRY3JFT.js} +1 -1
- package/dist/production/{context-m8rizgnE.d.mts → context-CmHpk1Ws.d.mts} +1 -1
- package/dist/production/dom-export.d.mts +3 -3
- package/dist/production/dom-export.d.ts +1 -1
- package/dist/production/dom-export.js +28 -28
- package/dist/production/dom-export.mjs +3 -3
- package/dist/production/{index-react-server-client-BLiUx67a.d.ts → index-react-server-client-CwU9bE5R.d.ts} +1 -1
- package/dist/production/{index-react-server-client-CdKROblb.d.mts → index-react-server-client-DPrDrCew.d.mts} +1 -1
- package/dist/production/index-react-server-client.d.mts +2 -2
- package/dist/production/index-react-server-client.d.ts +1 -1
- package/dist/production/index-react-server-client.js +4 -4
- package/dist/production/index-react-server-client.mjs +2 -2
- package/dist/production/index-react-server.js +1 -1
- package/dist/production/index-react-server.mjs +1 -1
- package/dist/production/index.d.mts +6 -6
- package/dist/production/index.d.ts +2 -2
- package/dist/production/index.js +85 -85
- package/dist/production/index.mjs +3 -3
- package/dist/production/lib/types/internal.js +1 -1
- package/dist/production/lib/types/internal.mjs +1 -1
- package/docs/explanation/backend-for-frontend.md +50 -0
- package/docs/explanation/code-splitting.md +61 -0
- package/docs/explanation/concurrency.md +135 -0
- package/docs/explanation/form-vs-fetcher.md +292 -0
- package/docs/explanation/hot-module-replacement.md +137 -0
- package/docs/explanation/hydration.md +14 -0
- package/docs/explanation/index-query-param.md +86 -0
- package/docs/explanation/index.md +4 -0
- package/docs/explanation/lazy-route-discovery.md +78 -0
- package/docs/explanation/location.md +6 -0
- package/docs/explanation/progressive-enhancement.md +150 -0
- package/docs/explanation/race-conditions.md +88 -0
- package/docs/explanation/react-transitions.md +160 -0
- package/docs/explanation/route-matching.md +7 -0
- package/docs/explanation/server-client-execution.md +4 -0
- package/docs/explanation/sessions-and-cookies.md +465 -0
- package/docs/explanation/special-files.md +16 -0
- package/docs/explanation/state-management.md +524 -0
- package/docs/explanation/styling.md +87 -0
- package/docs/explanation/type-safety.md +82 -0
- package/docs/how-to/accessibility.md +44 -0
- package/docs/how-to/client-data.md +199 -0
- package/docs/how-to/data-strategy.md +317 -0
- package/docs/how-to/error-boundary.md +231 -0
- package/docs/how-to/error-reporting.md +142 -0
- package/docs/how-to/fetchers.md +307 -0
- package/docs/how-to/file-route-conventions.md +410 -0
- package/docs/how-to/file-uploads.md +217 -0
- package/docs/how-to/form-validation.md +120 -0
- package/docs/how-to/headers.md +164 -0
- package/docs/how-to/index.md +4 -0
- package/docs/how-to/instrumentation.md +556 -0
- package/docs/how-to/meta.md +40 -0
- package/docs/how-to/middleware.md +763 -0
- package/docs/how-to/navigation-blocking.md +233 -0
- package/docs/how-to/optimize-revalidation.md +12 -0
- package/docs/how-to/pre-rendering.md +225 -0
- package/docs/how-to/presets.md +103 -0
- package/docs/how-to/react-server-components.md +899 -0
- package/docs/how-to/resource-routes.md +126 -0
- package/docs/how-to/route-module-type-safety.md +100 -0
- package/docs/how-to/search-params.md +4 -0
- package/docs/how-to/security.md +30 -0
- package/docs/how-to/server-bundles.md +66 -0
- package/docs/how-to/spa.md +120 -0
- package/docs/how-to/status.md +63 -0
- package/docs/how-to/suspense.md +132 -0
- package/docs/how-to/using-handle.md +117 -0
- package/docs/how-to/view-transitions.md +237 -0
- package/docs/how-to/webhook.md +50 -0
- package/docs/index.md +39 -0
- package/docs/start/data/actions.md +138 -0
- package/docs/start/data/custom.md +198 -0
- package/docs/start/data/data-loading.md +44 -0
- package/docs/start/data/index.md +4 -0
- package/docs/start/data/installation.md +52 -0
- package/docs/start/data/navigating.md +12 -0
- package/docs/start/data/pending-ui.md +12 -0
- package/docs/start/data/route-object.md +268 -0
- package/docs/start/data/routing.md +281 -0
- package/docs/start/data/testing.md +8 -0
- package/docs/start/declarative/index.md +4 -0
- package/docs/start/declarative/installation.md +43 -0
- package/docs/start/declarative/navigating.md +133 -0
- package/docs/start/declarative/routing.md +237 -0
- package/docs/start/declarative/url-values.md +65 -0
- package/docs/start/framework/actions.md +174 -0
- package/docs/start/framework/data-loading.md +201 -0
- package/docs/start/framework/deploying.md +96 -0
- package/docs/start/framework/index.md +4 -0
- package/docs/start/framework/installation.md +41 -0
- package/docs/start/framework/navigating.md +182 -0
- package/docs/start/framework/pending-ui.md +142 -0
- package/docs/start/framework/rendering.md +59 -0
- package/docs/start/framework/route-module.md +527 -0
- package/docs/start/framework/routing.md +362 -0
- package/docs/start/framework/testing.md +133 -0
- package/docs/start/index.md +4 -0
- package/docs/start/modes.md +201 -0
- package/docs/upgrading/component-routes.md +363 -0
- package/docs/upgrading/future.md +280 -0
- package/docs/upgrading/index.md +4 -0
- package/docs/upgrading/remix.md +403 -0
- package/docs/upgrading/router-provider.md +442 -0
- package/docs/upgrading/v6.md +382 -0
- package/package.json +2 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Form Validation
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Form Validation
|
|
6
|
+
|
|
7
|
+
[MODES: framework, data]
|
|
8
|
+
|
|
9
|
+
<br/>
|
|
10
|
+
<br/>
|
|
11
|
+
|
|
12
|
+
This guide walks through a simple signup form implementation. You will likely want to pair these concepts with third-party validation libraries and error components, but this guide only focuses on the moving pieces for React Router.
|
|
13
|
+
|
|
14
|
+
## 1. Setting Up
|
|
15
|
+
|
|
16
|
+
We'll start by creating a basic signup route with form.
|
|
17
|
+
|
|
18
|
+
```ts filename=app/routes.ts
|
|
19
|
+
import {
|
|
20
|
+
type RouteConfig,
|
|
21
|
+
route,
|
|
22
|
+
} from "@react-router/dev/routes";
|
|
23
|
+
|
|
24
|
+
export default [
|
|
25
|
+
route("signup", "signup.tsx"),
|
|
26
|
+
] satisfies RouteConfig;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```tsx filename=signup.tsx
|
|
30
|
+
import type { Route } from "./+types/signup";
|
|
31
|
+
import { useFetcher } from "react-router";
|
|
32
|
+
|
|
33
|
+
export default function Signup(_: Route.ComponentProps) {
|
|
34
|
+
let fetcher = useFetcher();
|
|
35
|
+
return (
|
|
36
|
+
<fetcher.Form method="post">
|
|
37
|
+
<p>
|
|
38
|
+
<input type="email" name="email" />
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
<p>
|
|
42
|
+
<input type="password" name="password" />
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<button type="submit">Sign Up</button>
|
|
46
|
+
</fetcher.Form>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 2. Defining the Action
|
|
52
|
+
|
|
53
|
+
In this step, we'll define a server `action` in the same file as our `Signup` component. Note that the aim here is to provide a broad overview of the mechanics involved rather than digging deep into form validation rules or error object structures. We'll use rudimentary checks for the email and password to demonstrate the core concepts.
|
|
54
|
+
|
|
55
|
+
```tsx filename=signup.tsx
|
|
56
|
+
import type { Route } from "./+types/signup";
|
|
57
|
+
import { redirect, useFetcher, data } from "react-router";
|
|
58
|
+
|
|
59
|
+
export default function Signup(_: Route.ComponentProps) {
|
|
60
|
+
// omitted for brevity
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function action({
|
|
64
|
+
request,
|
|
65
|
+
}: Route.ActionArgs) {
|
|
66
|
+
const formData = await request.formData();
|
|
67
|
+
const email = String(formData.get("email"));
|
|
68
|
+
const password = String(formData.get("password"));
|
|
69
|
+
|
|
70
|
+
const errors = {};
|
|
71
|
+
|
|
72
|
+
if (!email.includes("@")) {
|
|
73
|
+
errors.email = "Invalid email address";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (password.length < 12) {
|
|
77
|
+
errors.password =
|
|
78
|
+
"Password should be at least 12 characters";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (Object.keys(errors).length > 0) {
|
|
82
|
+
return data({ errors }, { status: 400 });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Redirect to dashboard if validation is successful
|
|
86
|
+
return redirect("/dashboard");
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If any validation errors are found, they are returned from the `action` to the fetcher. This is our way of signaling to the UI that something needs to be corrected, otherwise the user will be redirected to the dashboard.
|
|
91
|
+
|
|
92
|
+
Note the `data({ errors }, { status: 400 })` call. Setting a 400 status is the web standard way to signal to the client that there was a validation error (Bad Request). In React Router, only 2xx status codes trigger page data revalidation, so sending a 400 status prevents the normal revalidation that would occur after an `action`.
|
|
93
|
+
|
|
94
|
+
## 3. Displaying Validation Errors
|
|
95
|
+
|
|
96
|
+
Finally, we'll modify the `Signup` component to display validation errors, if any, from `fetcher.data`.
|
|
97
|
+
|
|
98
|
+
```tsx filename=signup.tsx lines=[3,8,13-15]
|
|
99
|
+
export default function Signup(_: Route.ComponentProps) {
|
|
100
|
+
let fetcher = useFetcher();
|
|
101
|
+
let errors = fetcher.data?.errors;
|
|
102
|
+
return (
|
|
103
|
+
<fetcher.Form method="post">
|
|
104
|
+
<p>
|
|
105
|
+
<input type="email" name="email" />
|
|
106
|
+
{errors?.email ? <em>{errors.email}</em> : null}
|
|
107
|
+
</p>
|
|
108
|
+
|
|
109
|
+
<p>
|
|
110
|
+
<input type="password" name="password" />
|
|
111
|
+
{errors?.password ? (
|
|
112
|
+
<em>{errors.password}</em>
|
|
113
|
+
) : null}
|
|
114
|
+
</p>
|
|
115
|
+
|
|
116
|
+
<button type="submit">Sign Up</button>
|
|
117
|
+
</fetcher.Form>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: HTTP Headers
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# HTTP Headers
|
|
6
|
+
|
|
7
|
+
[MODES: framework]
|
|
8
|
+
|
|
9
|
+
<br/>
|
|
10
|
+
<br/>
|
|
11
|
+
|
|
12
|
+
## Reading request headers
|
|
13
|
+
|
|
14
|
+
The `request` sent to route handlers is a standard Web Fetch [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request), so you can read headers directly from the [`request.headers`](https://developer.mozilla.org/en-US/docs/Web/API/Request/headers) property:
|
|
15
|
+
|
|
16
|
+
```tsx filename=some-route.tsx
|
|
17
|
+
export async function loader({
|
|
18
|
+
request,
|
|
19
|
+
}: Route.LoaderArgs) {
|
|
20
|
+
// Standard Headers methods are available
|
|
21
|
+
const userAgent = request.headers.get("User-Agent");
|
|
22
|
+
const hasCookies = request.headers.has("Cookie");
|
|
23
|
+
|
|
24
|
+
// ...
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Setting response headers
|
|
29
|
+
|
|
30
|
+
Headers are primarily defined with the route module `headers` export. You can also set headers in `entry.server.tsx`.
|
|
31
|
+
|
|
32
|
+
### From Route Modules
|
|
33
|
+
|
|
34
|
+
```tsx filename=some-route.tsx
|
|
35
|
+
import { Route } from "./+types/some-route";
|
|
36
|
+
|
|
37
|
+
export function headers(_: Route.HeadersArgs) {
|
|
38
|
+
return {
|
|
39
|
+
"Content-Security-Policy": "default-src 'self'",
|
|
40
|
+
"X-Frame-Options": "DENY",
|
|
41
|
+
"X-Content-Type-Options": "nosniff",
|
|
42
|
+
"Cache-Control": "max-age=3600, s-maxage=86400",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
You can return either a [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) instance or `HeadersInit`.
|
|
48
|
+
|
|
49
|
+
### From loaders and actions
|
|
50
|
+
|
|
51
|
+
When the header is dependent on loader data, loaders and actions can also set headers.
|
|
52
|
+
|
|
53
|
+
**1. Wrap your return value in `data`**
|
|
54
|
+
|
|
55
|
+
```tsx lines=[1,8]
|
|
56
|
+
import { data } from "react-router";
|
|
57
|
+
|
|
58
|
+
export async function loader({ params }: LoaderArgs) {
|
|
59
|
+
let [page, ms] = await fakeTimeCall(
|
|
60
|
+
await getPage(params.id),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return data(page, {
|
|
64
|
+
headers: {
|
|
65
|
+
"Server-Timing": `page;dur=${ms};desc="Page query"`,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**2. Return from `headers` export**
|
|
72
|
+
|
|
73
|
+
Headers from loaders and actions are not sent automatically. You must explicitly return them from the `headers` export.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
function hasAnyHeaders(headers: Headers): boolean {
|
|
77
|
+
return [...headers].length > 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function headers({
|
|
81
|
+
actionHeaders,
|
|
82
|
+
loaderHeaders,
|
|
83
|
+
}: HeadersArgs) {
|
|
84
|
+
return hasAnyHeaders(actionHeaders)
|
|
85
|
+
? actionHeaders
|
|
86
|
+
: loaderHeaders;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
One notable exception is `Set-Cookie` headers, which are automatically preserved from `headers`, `loader`, and `action` in parent routes, even without exporting `headers` from the child route.
|
|
91
|
+
|
|
92
|
+
### Merging with parent headers
|
|
93
|
+
|
|
94
|
+
Consider these nested routes
|
|
95
|
+
|
|
96
|
+
```ts filename=routes.ts
|
|
97
|
+
route("pages", "pages-layout-with-nav.tsx", [
|
|
98
|
+
route(":slug", "page.tsx"),
|
|
99
|
+
]);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If both route modules want to set headers, the headers from the deepest matching route will be sent.
|
|
103
|
+
|
|
104
|
+
When you need to keep both the parent and the child headers, you need to merge them in the child route.
|
|
105
|
+
|
|
106
|
+
#### Appending
|
|
107
|
+
|
|
108
|
+
The easiest way is to simply append to the parent headers. This avoids overwriting a header the parent may have set and both are important.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
export function headers({ parentHeaders }: HeadersArgs) {
|
|
112
|
+
parentHeaders.append(
|
|
113
|
+
"Permissions-Policy: geolocation=()",
|
|
114
|
+
);
|
|
115
|
+
return parentHeaders;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### Setting
|
|
120
|
+
|
|
121
|
+
Sometimes it's important to overwrite the parent header. Do this with `set` instead of `append`:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
export function headers({ parentHeaders }: HeadersArgs) {
|
|
125
|
+
parentHeaders.set(
|
|
126
|
+
"Cache-Control",
|
|
127
|
+
"max-age=3600, s-maxage=86400",
|
|
128
|
+
);
|
|
129
|
+
return parentHeaders;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
You can avoid the need to merge headers by only defining headers in "leaf routes" (index routes and child routes without children) and not in parent routes.
|
|
134
|
+
|
|
135
|
+
### From `entry.server.tsx`
|
|
136
|
+
|
|
137
|
+
The `handleRequest` export receives the headers from the route module as an argument. You can append global headers here.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
export default async function handleRequest(
|
|
141
|
+
request,
|
|
142
|
+
responseStatusCode,
|
|
143
|
+
responseHeaders,
|
|
144
|
+
routerContext,
|
|
145
|
+
loadContext,
|
|
146
|
+
) {
|
|
147
|
+
// set, append global headers
|
|
148
|
+
responseHeaders.set(
|
|
149
|
+
"X-App-Version",
|
|
150
|
+
routerContext.manifest.version,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return new Response(await getStream(), {
|
|
154
|
+
headers: responseHeaders,
|
|
155
|
+
status: responseStatusCode,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
If you don't have an `entry.server.tsx` run the `reveal` command:
|
|
161
|
+
|
|
162
|
+
```shellscript nonumber
|
|
163
|
+
react-router reveal
|
|
164
|
+
```
|