Skip to content

Commit 4d58d0e

Browse files
committed
Revert "Revert "Add better control over submission serialization (#10342)""
This reverts commit f92aa2e.
1 parent 1f294a3 commit 4d58d0e

14 files changed

+1115
-140
lines changed
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

+105
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

+10
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;
@@ -132,6 +133,8 @@ export function useIdleLogout() {
132133
}
133134
```
134135

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

137140
If you find yourself calling this function inside of click handlers, you can probably simplify your code by using `<fetcher.Form>` instead.
@@ -200,6 +203,8 @@ function TaskCheckbox({ task }) {
200203
}
201204
```
202205

206+
If you opt-out of serialization using `encType: null`, then `fetcher.formData` will be `undefined` and your data will be exposed on `fetcher.payload`.
207+
203208
## `fetcher.formAction`
204209

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

225230
<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>
226231

232+
## `fetcher.payload`
233+
234+
Any POST, PUT, PATCH, or DELETE that started from a `fetcher.submit(payload, { encType: null })` will have your `payload` value represented in `fetcher.payload`.
235+
227236
[loader]: ../route/loader
228237
[action]: ../route/action
229238
[pickingarouter]: ../routers/picking-a-router
230239
[indexsearchparam]: ../guides/index-search-param
231240
[link]: ../components/link
232241
[form]: ../components/form
233242
[api-development-strategy]: ../guides/api-development-strategy
243+
[use-submit]: ./use-submit.md

docs/hooks/use-navigation.md

+7
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.

docs/hooks/use-submit.md

+44
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,50 @@ formData.append("cheese", "gouda");
7878
submit(formData);
7979
```
8080

81+
### Payload Serialization
82+
83+
You may also submit raw JSON to your `action` and the default behavior will be to encode the key/values into `FormData`:
84+
85+
```tsx
86+
let obj = { key: "value" };
87+
submit(obj); // -> request.formData()
88+
```
89+
90+
You may also choose which type of serialization you'd like via the `encType` option:
91+
92+
```tsx
93+
let obj = { key: "value" };
94+
submit(obj, {
95+
encType: "application/x-www-form-urlencoded",
96+
}); // -> request.formData()
97+
```
98+
99+
```tsx
100+
let obj = { key: "value" };
101+
submit(obj, { encType: "application/json" }); // -> request.json()
102+
```
103+
104+
```tsx
105+
let text = "Plain ol' text";
106+
submit(obj, { encType: "text/plain" }); // -> request.text()
107+
```
108+
109+
<docs-warn>In future versions of React Router, the default behavior will not serialize raw JSON payloads. If you are submitting raw JSON today it's recommended to specify an explicit `encType`.</docs-warn>
110+
111+
### Opting out of serialization
112+
113+
Sometimes in a client-side application, it's overkill to require serialization into `request.formData` when you have a raw JSON object in your component and want to submit it to your `action` directly. If you'd like to opt out of serialization, you can pass `encType: null` to your second options argument, and your data will be sent to your action function verbatim as a `payload` parameter:
114+
115+
```tsx
116+
let obj = { key: "value" };
117+
submit(obj, { encType: null });
118+
119+
function action({ request, payload }) {
120+
// payload is `obj` from your component
121+
// request.body === null
122+
}
123+
```
124+
81125
## Submit options
82126

83127
The second argument is a set of options that map directly to form submission attributes:

docs/route/action.md

+17
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,22 @@ formData.get("lyrics");
101101

102102
For more information on `formData` see [Working with FormData][workingwithformdata].
103103

104+
### Opt-in serialization types
105+
106+
Note that when using [`useSubmit`][usesubmit] you may also pass `encType: "application/json"` or `encType: "text/plain"` to instead serialize your payload into `request.json()` or `request.text()`.
107+
108+
## `payload`
109+
110+
A `payload` is provided to your action when you submit imperatively with [`useSubmit`][usesubmit] and provide a raw javascript value. This value might also be serialized into the request depending on the `encType`.
111+
112+
```jsx
113+
function Component {
114+
let submit = useSubmit();
115+
submit({ key: "value" }, { encType: null });
116+
// action payload is { key: 'value' }
117+
}
118+
```
119+
104120
## Returning Responses
105121

106122
While you can return anything you want from an action and get access to it from [`useActionData`][useactiondata], you can also return a web [Response][response].
@@ -144,5 +160,6 @@ For more details and expanded use cases, read the [errorElement][errorelement] d
144160
[form]: ../components/form
145161
[workingwithformdata]: ../guides/form-data
146162
[useactiondata]: ../hooks/use-action-data
163+
[usesubmit]: ../hooks/use-submit
147164
[returningresponses]: ./loader#returning-responses
148165
[createbrowserrouter]: ../routers/create-browser-router

docs/route/should-revalidate.md

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ interface ShouldRevalidateFunction {
6666
formAction?: Submission["formAction"];
6767
formEncType?: Submission["formEncType"];
6868
formData?: Submission["formData"];
69+
payload?: Submission["payload"];
6970
actionResult?: DataResult;
7071
defaultShouldRevalidate: boolean;
7172
}): boolean;

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@
114114
"none": "15.4 kB"
115115
},
116116
"packages/react-router-dom/dist/react-router-dom.production.min.js": {
117-
"none": "11.8 kB"
117+
"none": "12 kB"
118118
},
119119
"packages/react-router-dom/dist/umd/react-router-dom.production.min.js": {
120-
"none": "17.7 kB"
120+
"none": "17.9 kB"
121121
}
122122
}
123123
}

0 commit comments

Comments
 (0)