Skip to content

Commit

Permalink
Merge branch 'dev' into brophdawg11/link-invalid-to
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed Apr 21, 2023
2 parents 9d087fc + 4357e37 commit f533024
Show file tree
Hide file tree
Showing 16 changed files with 1,918 additions and 200 deletions.
61 changes: 61 additions & 0 deletions .changeset/direct-handlers-router.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
"@remix-run/router": minor
---

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

**Defining an `action` at the callsite:**

```jsx
let routes = [{ path: '/' }]; // No action on route

// Custom actions will behave as a submission navigation to the current location
router.navigate(null, {
formMethod "post",
formData: new FormData(),
action() {
// You may now define your custom action here
}
})
```

**Overriding an `action` at the call-site:**

```jsx
let routes = [{ path: '/', action: someAction }];
router.navigate(null, {
formMethod "post",
formData: new FormData(),
action() {
// This will be called instead of `someAction`
}
})
```

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

**Fetching to a direct loader without a defined route:**

```jsx
let routes = [{ path: "/", action: someAction }];
// Note no location required
router.fetch("key", "0", null, {
loader() {
// Call this loader for the fetcher and avoid the need for a resource route
},
});
```

**Fetching to a direct action without a defined route:**

```jsx
let routes = [{ path: '/', action: someAction }];
// Note no location required
router.fetch("key", "0", null, {
formMethod "post",
formData: new FormData(),
action() {
// Call this action for the fetcher and avoid the need for a resource route
}
})
```
51 changes: 51 additions & 0 deletions .changeset/direct-handlers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
"react-router-dom": minor
---

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.

**`useSubmit:`**

```jsx
let router = createBrowserRouter([
{
path: "/",
Component() {
let submit = useSubmit();

submit(data, {
formMethod: "post",
encType: null,
action({ payload }) {
// You may now define your action here
},
});
},
},
]);
```

**`fetcher.load`/`fetcher.submit`:**

```jsx
let router = createBrowserRouter([
{
path: "/",
Component() {
let fetcher = useFetcher();

fetcher.load(() => {
// You may now define a loader here
});

fetcher.submit(data, {
formMethod: "post",
encType: null,
action({ payload }) {
// You may now define your action here
},
});
},
},
]);
```
20 changes: 20 additions & 0 deletions .changeset/raw-payload-submission-router.md
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()`
105 changes: 105 additions & 0 deletions .changeset/raw-payload-submission.md
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"
}
```
32 changes: 32 additions & 0 deletions docs/hooks/use-fetcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function SomeComponent() {
// build your UI with these properties
fetcher.state;
fetcher.formData;
fetcher.payload;
fetcher.formMethod;
fetcher.formAction;
fetcher.data;
Expand Down Expand Up @@ -107,6 +108,16 @@ If you find yourself calling this function inside of click handlers, you can pro

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

### Direct `loader` specification

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`:

```tsx
fetcher.load(() => {
// Custom loader implementation here
});
```

## `fetcher.submit()`

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.
Expand All @@ -132,10 +143,24 @@ export function useIdleLogout() {
}
```

`fetcher.submit` is a wrapper around a [`useSubmit`][use-submit] call for the fetcher instance, so it also accepts the same options as `useSubmit`.

If you want to submit to an index route, use the [`?index` param][indexsearchparam].

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

### Direct `action` specification

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`:

```tsx
fetcher.submit(data, {
action({ payload }) {
// Custom action implementation here
},
});
```

## `fetcher.data`

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.
Expand Down Expand Up @@ -200,6 +225,8 @@ function TaskCheckbox({ task }) {
}
```

If you opt-out of serialization using `encType: null`, then `fetcher.formData` will be `undefined` and your data will be exposed on `fetcher.payload`.

## `fetcher.formAction`

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

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

## `fetcher.payload`

Any POST, PUT, PATCH, or DELETE that started from a `fetcher.submit(payload, { encType: null })` will have your `payload` value represented in `fetcher.payload`.

[loader]: ../route/loader
[action]: ../route/action
[pickingarouter]: ../routers/picking-a-router
[indexsearchparam]: ../guides/index-search-param
[link]: ../components/link
[form]: ../components/form
[api-development-strategy]: ../guides/api-development-strategy
[use-submit]: ./use-submit.md
7 changes: 7 additions & 0 deletions docs/hooks/use-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function SomeComponent() {
navigation.state;
navigation.location;
navigation.formData;
navigation.payload;
navigation.formAction;
navigation.formMethod;
}
Expand Down Expand Up @@ -90,8 +91,14 @@ let isRedirecting =

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.

If you opt-out of serialization using `encType: null`, then `navigation.formData` will be `undefined` and your data will be exposed on `navigation.payload`.

In the case of a GET form submission, `formData` will be empty and the data will be reflected in `navigation.location.search`.

## `navigation.payload`

Any POST, PUT, PATCH, or DELETE navigation that started from a `useSubmit(payload, { encType: null })` will have your `payload` value represented in `navigation.payload`.

## `navigation.location`

This tells you what the next [location][location] is going to be.
Expand Down
Loading

0 comments on commit f533024

Please sign in to comment.