Skip to content

Commit f533024

Browse files
committed
Merge branch 'dev' into brophdawg11/link-invalid-to
2 parents 9d087fc + 4357e37 commit f533024

16 files changed

+1918
-200
lines changed

.changeset/direct-handlers-router.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
"@remix-run/router": minor
3+
---
4+
5+
- Add support for direct `action` functions to be passed to `router.navigate`. This allows you to skip the creation of a new route to handle the `action` , or you can also override the defined route `action` at the call-site.
6+
7+
**Defining an `action` at the callsite:**
8+
9+
```jsx
10+
let routes = [{ path: '/' }]; // No action on route
11+
12+
// Custom actions will behave as a submission navigation to the current location
13+
router.navigate(null, {
14+
formMethod "post",
15+
formData: new FormData(),
16+
action() {
17+
// You may now define your custom action here
18+
}
19+
})
20+
```
21+
22+
**Overriding an `action` at the call-site:**
23+
24+
```jsx
25+
let routes = [{ path: '/', action: someAction }];
26+
router.navigate(null, {
27+
formMethod "post",
28+
formData: new FormData(),
29+
action() {
30+
// This will be called instead of `someAction`
31+
}
32+
})
33+
```
34+
35+
- Add support for direct `action`/`loader` functions to be passed to `router.fetch`. This allows you to skip the creation of a new route to handle the `loader` or `action`, or you can also override the defined route `loader` or `action` at the call-site.
36+
37+
**Fetching to a direct loader without a defined route:**
38+
39+
```jsx
40+
let routes = [{ path: "/", action: someAction }];
41+
// Note no location required
42+
router.fetch("key", "0", null, {
43+
loader() {
44+
// Call this loader for the fetcher and avoid the need for a resource route
45+
},
46+
});
47+
```
48+
49+
**Fetching to a direct action without a defined route:**
50+
51+
```jsx
52+
let routes = [{ path: '/', action: someAction }];
53+
// Note no location required
54+
router.fetch("key", "0", null, {
55+
formMethod "post",
56+
formData: new FormData(),
57+
action() {
58+
// Call this action for the fetcher and avoid the need for a resource route
59+
}
60+
})
61+
```

.changeset/direct-handlers.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
"react-router-dom": minor
3+
---
4+
5+
Add direct `action` function support to `useSubmit`/`fetcher.submit` and direct `loader` support to `fetcher.load`. This allows you to skip the creation of a new route to handle the `action` or `loader`. If both a call-site handler and a route-defined handler exist, the call-site handler will be used.
6+
7+
**`useSubmit:`**
8+
9+
```jsx
10+
let router = createBrowserRouter([
11+
{
12+
path: "/",
13+
Component() {
14+
let submit = useSubmit();
15+
16+
submit(data, {
17+
formMethod: "post",
18+
encType: null,
19+
action({ payload }) {
20+
// You may now define your action here
21+
},
22+
});
23+
},
24+
},
25+
]);
26+
```
27+
28+
**`fetcher.load`/`fetcher.submit`:**
29+
30+
```jsx
31+
let router = createBrowserRouter([
32+
{
33+
path: "/",
34+
Component() {
35+
let fetcher = useFetcher();
36+
37+
fetcher.load(() => {
38+
// You may now define a loader here
39+
});
40+
41+
fetcher.submit(data, {
42+
formMethod: "post",
43+
encType: null,
44+
action({ payload }) {
45+
// You may now define your action here
46+
},
47+
});
48+
},
49+
},
50+
]);
51+
```
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@remix-run/router": minor
3+
---
4+
5+
Add support for a new `payload` parameter for `router.navigate`/`router.fetch` submissions. This allows you to submit data to an `action` without requiring serialization into a `FormData` instance. This `payload` value will be passed unaltered to your `action` function.
6+
7+
```js
8+
router.navigate("/", { payload: { key: "value" } });
9+
10+
function action({ request, payload }) {
11+
// payload => { key: 'value' }
12+
// request.body => null
13+
}
14+
```
15+
16+
You may also opt-into serialization of this `payload` into your `request` using the `formEncType` parameter:
17+
18+
- `formEncType: "application/x-ww-form-urlencoded"` => serializes into `request.formData()`
19+
- `formEncType: "application/json"` => serializes into `request.json()`
20+
- `formEncType: "text/plain"` => serializes into `request.text()`

.changeset/raw-payload-submission.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
"react-router-dom": minor
3+
---
4+
5+
- Support better submission and control of serialization of raw payloads through `useSubmit`/`fetcher.submit`. The default `encType` will still be `application/x-www-form-urlencoded` as it is today, but actions will now also receive a raw `payload` parameter when you submit a raw value (not an HTML element, `FormData`, or `URLSearchParams`).
6+
7+
The default behavior will still serialize into `FormData`:
8+
9+
```jsx
10+
function Component() {
11+
let submit = useSubmit();
12+
submit({ key: "value" });
13+
// navigation.formEncType => "application/x-www-form-urlencoded"
14+
// navigation.formData => FormData instance
15+
// navigation.payload => { key: "Value" }
16+
}
17+
18+
function action({ request, payload }) {
19+
// request.headers.get("Content-Type") => "application/x-www-form-urlencoded"
20+
// request.formData => FormData instance
21+
// payload => { key: 'value' }
22+
}
23+
```
24+
25+
You may opt out of this default serialization using `encType: null`:
26+
27+
```jsx
28+
function Component() {
29+
let submit = useSubmit();
30+
submit({ key: "value" }, { encType: null });
31+
// navigation.formEncType => null
32+
// navigation.formData => undefined
33+
// navigation.payload => { key: "Value" }
34+
}
35+
36+
function action({ request, payload }) {
37+
// request.headers.get("Content-Type") => null
38+
// request.formData => undefined
39+
// payload => { key: 'value' }
40+
}
41+
```
42+
43+
_Note: we plan to change the default behavior of `{ encType: undefined }` to match this "no serialization" behavior in React Router v7. In order to better prepare for this change, we encourage developers to add explicit content types to scenarios in which they are submitting raw JSON objects:_
44+
45+
```jsx
46+
function Component() {
47+
let submit = useSubmit();
48+
49+
// Change this:
50+
submit({ key: "value" });
51+
52+
// To this:
53+
submit({ key: "value" }, { encType: "application/x-www-form-urlencoded" });
54+
}
55+
```
56+
57+
- You may now also opt-into different types of serialization of this `payload` into your `request` using the `formEncType` parameter:
58+
59+
```js
60+
function Component() {
61+
let submit = useSubmit();
62+
submit({ key: "value" }, { encType: "application/json" });
63+
// navigation.formEncType => "application/json"
64+
// navigation.formData => undefined
65+
// navigation.payload => { key: "Value" }
66+
}
67+
68+
function action({ request, payload }) {
69+
// request.headers.get("Content-Type") => "application/json"
70+
// request.json => { key: 'value' }
71+
// payload => { key: 'value' }
72+
}
73+
```
74+
75+
```js
76+
function Component() {
77+
let submit = useSubmit();
78+
submit({ key: "value" }, { encType: "application/x-www-form-urlencoded" });
79+
// navigation.formEncType => "application/x-www-form-urlencoded"
80+
// navigation.formData => FormData instance
81+
// navigation.payload => { key: "Value" }
82+
}
83+
84+
function action({ request, payload }) {
85+
// request.headers.get("Content-Type") => "application/x-www-form-urlencoded"
86+
// request.formData => { key: 'value' }
87+
// payload => { key: 'value' }
88+
}
89+
```
90+
91+
```js
92+
function Component() {
93+
let submit = useSubmit();
94+
submit("Plain ol' text", { encType: "text/plain" });
95+
// navigation.formEncType => "text/plain"
96+
// navigation.formData => undefined
97+
// navigation.payload => "Plain ol' text"
98+
}
99+
100+
function action({ request, payload }) {
101+
// request.headers.get("Content-Type") => "text/plain"
102+
// request.text => "Plain ol' text"
103+
// payload => "Plain ol' text"
104+
}
105+
```

docs/hooks/use-fetcher.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ function SomeComponent() {
3838
// build your UI with these properties
3939
fetcher.state;
4040
fetcher.formData;
41+
fetcher.payload;
4142
fetcher.formMethod;
4243
fetcher.formAction;
4344
fetcher.data;
@@ -107,6 +108,16 @@ If you find yourself calling this function inside of click handlers, you can pro
107108

108109
<docs-info>Any `fetcher.load` calls that are active on the page will be re-executed as part of revalidation (either after a navigation submission, another fetcher submission, or a `useRevalidator()` call)</docs-info>
109110

111+
### Direct `loader` specification
112+
113+
If you want to perform a `fetcher.load`, but you don't want/need to create a route for your `loader`, you can pass a `loader` directly to `fetcher.load`:
114+
115+
```tsx
116+
fetcher.load(() => {
117+
// Custom loader implementation here
118+
});
119+
```
120+
110121
## `fetcher.submit()`
111122

112123
The imperative version of `<fetcher.Form>`. If a user interaction should initiate the fetch, you should use `<fetcher.Form>`. But if you, the programmer are initiating the fetch (not in response to a user clicking a button, etc.), then use this function.
@@ -132,10 +143,24 @@ export function useIdleLogout() {
132143
}
133144
```
134145

146+
`fetcher.submit` is a wrapper around a [`useSubmit`][use-submit] call for the fetcher instance, so it also accepts the same options as `useSubmit`.
147+
135148
If you want to submit to an index route, use the [`?index` param][indexsearchparam].
136149

137150
If you find yourself calling this function inside of click handlers, you can probably simplify your code by using `<fetcher.Form>` instead.
138151

152+
### Direct `action` specification
153+
154+
If you want to perform a `fetcher.submit`, but you don't want/need to create a route for your `action`, you can pass an `action` directly to `fetcher.submit`:
155+
156+
```tsx
157+
fetcher.submit(data, {
158+
action({ payload }) {
159+
// Custom action implementation here
160+
},
161+
});
162+
```
163+
139164
## `fetcher.data`
140165

141166
The returned data from the loader or action is stored here. Once the data is set, it persists on the fetcher even through reloads and resubmissions.
@@ -200,6 +225,8 @@ function TaskCheckbox({ task }) {
200225
}
201226
```
202227

228+
If you opt-out of serialization using `encType: null`, then `fetcher.formData` will be `undefined` and your data will be exposed on `fetcher.payload`.
229+
203230
## `fetcher.formAction`
204231

205232
Tells you the action url the form is being submitted to.
@@ -224,10 +251,15 @@ fetcher.formMethod; // "post"
224251

225252
<docs-warning>The `fetcher.formMethod` field is lowercase without the `future.v7_normalizeFormMethod` [Future Flag][api-development-strategy]. This is being normalized to uppercase to align with the `fetch()` behavior in v7, so please upgrade your React Router v6 applications to adopt the uppercase HTTP methods.</docs-warning>
226253

254+
## `fetcher.payload`
255+
256+
Any POST, PUT, PATCH, or DELETE that started from a `fetcher.submit(payload, { encType: null })` will have your `payload` value represented in `fetcher.payload`.
257+
227258
[loader]: ../route/loader
228259
[action]: ../route/action
229260
[pickingarouter]: ../routers/picking-a-router
230261
[indexsearchparam]: ../guides/index-search-param
231262
[link]: ../components/link
232263
[form]: ../components/form
233264
[api-development-strategy]: ../guides/api-development-strategy
265+
[use-submit]: ./use-submit.md

docs/hooks/use-navigation.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ function SomeComponent() {
2323
navigation.state;
2424
navigation.location;
2525
navigation.formData;
26+
navigation.payload;
2627
navigation.formAction;
2728
navigation.formMethod;
2829
}
@@ -90,8 +91,14 @@ let isRedirecting =
9091

9192
Any POST, PUT, PATCH, or DELETE navigation that started from a `<Form>` or `useSubmit` will have your form's submission data attached to it. This is primarily useful to build "Optimistic UI" with the `submission.formData` [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object.
9293

94+
If you opt-out of serialization using `encType: null`, then `navigation.formData` will be `undefined` and your data will be exposed on `navigation.payload`.
95+
9396
In the case of a GET form submission, `formData` will be empty and the data will be reflected in `navigation.location.search`.
9497

98+
## `navigation.payload`
99+
100+
Any POST, PUT, PATCH, or DELETE navigation that started from a `useSubmit(payload, { encType: null })` will have your `payload` value represented in `navigation.payload`.
101+
95102
## `navigation.location`
96103

97104
This tells you what the next [location][location] is going to be.

0 commit comments

Comments
 (0)