-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add better control over submission serialization #10342
Changes from all commits
f963974
66aa3ab
d424514
b01d57e
c945785
67a7444
ef95de9
4b77992
7e7768e
01656c7
42c72aa
13e6b8e
8760827
91f97ba
97425bc
046bca4
e1c71cb
99c9a0e
5cdbdab
d8dc40a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
--- | ||
"@remix-run/router": minor | ||
--- | ||
|
||
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. | ||
|
||
```js | ||
router.navigate("/", { payload: { key: "value" } }); | ||
|
||
function action({ request, payload }) { | ||
// payload => { key: 'value' } | ||
// request.body => null | ||
} | ||
``` | ||
|
||
You may also opt-into serialization of this `payload` into your `request` using the `formEncType` parameter: | ||
|
||
- `formEncType: "application/x-ww-form-urlencoded"` => serializes into `request.formData()` | ||
- `formEncType: "application/json"` => serializes into `request.json()` | ||
- `formEncType: "text/plain"` => serializes into `request.text()` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
--- | ||
"react-router-dom": minor | ||
--- | ||
|
||
- 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`). | ||
|
||
The default behavior will still serialize into `FormData`: | ||
|
||
```jsx | ||
function Component() { | ||
let submit = useSubmit(); | ||
submit({ key: "value" }); | ||
// navigation.formEncType => "application/x-www-form-urlencoded" | ||
// navigation.formData => FormData instance | ||
// navigation.payload => { key: "Value" } | ||
} | ||
|
||
function action({ request, payload }) { | ||
// request.headers.get("Content-Type") => "application/x-www-form-urlencoded" | ||
// request.formData => FormData instance | ||
// payload => { key: 'value' } | ||
} | ||
``` | ||
|
||
You may opt out of this default serialization using `encType: null`: | ||
|
||
```jsx | ||
function Component() { | ||
let submit = useSubmit(); | ||
submit({ key: "value" }, { encType: null }); | ||
// navigation.formEncType => null | ||
// navigation.formData => undefined | ||
// navigation.payload => { key: "Value" } | ||
} | ||
|
||
function action({ request, payload }) { | ||
// request.headers.get("Content-Type") => null | ||
// request.formData => undefined | ||
// payload => { key: 'value' } | ||
} | ||
``` | ||
|
||
_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:_ | ||
|
||
```jsx | ||
function Component() { | ||
let submit = useSubmit(); | ||
|
||
// Change this: | ||
submit({ key: "value" }); | ||
|
||
// To this: | ||
submit({ key: "value" }, { encType: "application/x-www-form-urlencoded" }); | ||
} | ||
``` | ||
|
||
- You may now also opt-into different types of serialization of this `payload` into your `request` using the `formEncType` parameter: | ||
|
||
```js | ||
function Component() { | ||
let submit = useSubmit(); | ||
submit({ key: "value" }, { encType: "application/json" }); | ||
// navigation.formEncType => "application/json" | ||
// navigation.formData => undefined | ||
// navigation.payload => { key: "Value" } | ||
} | ||
|
||
function action({ request, payload }) { | ||
// request.headers.get("Content-Type") => "application/json" | ||
// request.json => { key: 'value' } | ||
// payload => { key: 'value' } | ||
} | ||
``` | ||
|
||
```js | ||
function Component() { | ||
let submit = useSubmit(); | ||
submit({ key: "value" }, { encType: "application/x-www-form-urlencoded" }); | ||
// navigation.formEncType => "application/x-www-form-urlencoded" | ||
// navigation.formData => FormData instance | ||
// navigation.payload => { key: "Value" } | ||
} | ||
|
||
function action({ request, payload }) { | ||
// request.headers.get("Content-Type") => "application/x-www-form-urlencoded" | ||
// request.formData => { key: 'value' } | ||
// payload => { key: 'value' } | ||
} | ||
``` | ||
|
||
```js | ||
function Component() { | ||
let submit = useSubmit(); | ||
submit("Plain ol' text", { encType: "text/plain" }); | ||
// navigation.formEncType => "text/plain" | ||
// navigation.formData => undefined | ||
// navigation.payload => "Plain ol' text" | ||
} | ||
|
||
function action({ request, payload }) { | ||
// request.headers.get("Content-Type") => "text/plain" | ||
// request.text => "Plain ol' text" | ||
// payload => "Plain ol' text" | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,6 +78,48 @@ formData.append("cheese", "gouda"); | |
submit(formData); | ||
``` | ||
|
||
### Payload Serialization | ||
|
||
You may also submit raw JSON to your `action` and the default behavior will be to encode the key/values into `FormData`: | ||
|
||
```tsx | ||
let obj = { key: "value" }; | ||
submit(obj); // -> request.formData() | ||
``` | ||
|
||
You may also choose which type of serialization you'd like via the `encType` option: | ||
|
||
```tsx | ||
let obj = { key: "value" }; | ||
submit(obj, { encType: 'application/x-www-form-urlencoded' }); // -> request.formData() | ||
``` | ||
|
||
```tsx | ||
let obj = { key: "value" }; | ||
submit(obj, { encType: "application/json" }); // -> request.json() | ||
``` | ||
|
||
```tsx | ||
let text = "Plain ol' text"; | ||
submit(obj, { encType: "text/plain" }); // -> request.text() | ||
``` | ||
|
||
<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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have we talked about making this a future flag? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ryan and I talked through this a good bit and landed on a heuristic that I need to get added to our docs and specifically this diagram. A future flag is needed when there is no way to opt-in to the new (breaking) behavior at the call-site (locally), so we need to give them a config (global) opt-in flag. For example:
So that diagram needs a new path for breaking changes where we say "local opt-in possible?" and if so we can just log deprecation warnings and don't need to introduce a future flag. |
||
|
||
### Opting out of serialization | ||
|
||
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: | ||
|
||
```tsx | ||
let obj = { key: "value" }; | ||
submit(obj, { encType: null }); | ||
|
||
function action({ request, payload }) { | ||
// payload is `obj` from your component | ||
// request.body === null | ||
} | ||
``` | ||
|
||
## Submit options | ||
|
||
The second argument is a set of options that map directly to form submission attributes: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be additional type-checking for this use case (and I guess when explicitly setting
encType: 'application/x-www-form-urlencoded'
)?If I understand correctly, it seems like a big footgun that I can now pass any type here and I won't get a type error and then it'll blow up later when the code assumes it can be serialized to FormData.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably 😬 . #10362 starts adding some more advanced type checks around
payload
/encType
/action
so we can try to add it there - but it's proven a bit tricky thus far to get the inferred types working right across params. I'm sure we can figure something out though. I'll drop a note over there