Skip to content

Commit c0dbcd2

Browse files
authored
Add useFetcher(key) and <Form navigate={false}> (#10960)
1 parent 805924d commit c0dbcd2

File tree

11 files changed

+560
-129
lines changed

11 files changed

+560
-129
lines changed

.changeset/fetcher-key.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"react-router-dom": minor
3+
---
4+
5+
Add support for manual fetcher key specification via `useFetcher({ key: string })` so you can access the same fetcher instance from different components in your application without prop-drilling ([RFC](https://github.com/remix-run/remix/discussions/7698))
6+
7+
- Fetcher keys are now also exposed on the fetchers returned from `useFetchers` so that they can be looked up by `key`
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/router": patch
3+
---
4+
5+
Fix `router.getFetcher`/`router.deleteFetcher` type definitions which incorrectly specified `key` as an optional parameter

.changeset/form-navigate-false.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"react-router-dom": minor
3+
---
4+
5+
Add `navigate`/`fetcherKey` params/props to `useSumbit`/`Form` to support kicking off a fetcher submission under the hood with an optionally user-specified `key`
6+
7+
- Invoking a fetcher in this way is ephemeral and stateless
8+
- If you need to access the state of one of these fetchers, you will need to leverage `useFetcher({ key })` to look it up elsewhere

docs/components/form.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ function Project() {
197197

198198
As you can see, both forms submit to the same route but you can use the `request.method` to branch on what you intend to do. After the actions completes, the `loader` will be revalidated and the UI will automatically synchronize with the new data.
199199

200+
## `navigate`
201+
202+
You can tell the form to skip the navigation and use a [fetcher][usefetcher] internally by specifying `<Form navigate={false}>`. This is essentially a shorthand for `useFetcher()` + `<fetcher.Form>` where you don't care about the resulting data and only want to kick off a submission and access the pending state via [`useFetchers()`][usefetchers].
203+
204+
## `fetcherKey`
205+
206+
When using a non-navigating `Form`, you may also optionally specify your own fetcher key to use via `<Form navigate={false} fetcherKey="my-key">`.
207+
200208
## `replace`
201209

202210
Instructs the form to replace the current entry in the history stack, instead of pushing the new entry.
@@ -367,6 +375,7 @@ You can access those values from the `request.url`
367375
[useactiondata]: ../hooks/use-action-data
368376
[formdata]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
369377
[usefetcher]: ../hooks/use-fetcher
378+
[usefetchers]: ../hooks/use-fetchers
370379
[htmlform]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form
371380
[htmlformaction]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-action
372381
[htmlform-method]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method

docs/hooks/use-fetcher.md

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,37 @@ Fetchers have a lot of built-in behavior:
6060
- Handles uncaught errors by rendering the nearest `errorElement` (just like a normal navigation from `<Link>` or `<Form>`)
6161
- Will redirect the app if your action/loader being called returns a redirect (just like a normal navigation from `<Link>` or `<Form>`)
6262

63-
## `fetcher.state`
63+
## Options
6464

65-
You can know the state of the fetcher with `fetcher.state`. It will be one of:
65+
### `key`
6666

67-
- **idle** - nothing is being fetched.
68-
- **submitting** - A route action is being called due to a fetcher submission using POST, PUT, PATCH, or DELETE
69-
- **loading** - The fetcher is calling a loader (from a `fetcher.load`) or is being revalidated after a separate submission or `useRevalidator` call
67+
By default, `useFetcher` generate a unique fetcher scoped to that component (however, it may be looked up in [`useFetchers()`][use_fetchers] while in-flight). If you want to identify a fetcher with your own key such that you can access it from elsewhere in your app, you can do that with the `key` option:
68+
69+
```tsx
70+
function AddToBagButton() {
71+
const fetcher = useFetcher({ key: "add-to-bag" });
72+
return <fetcher.Form method="post">...</fetcher.Form>;
73+
}
7074

71-
## `fetcher.Form`
75+
// Then, up in the header...
76+
function CartCount({ count }) {
77+
const fetcher = useFetcher({ key: "add-to-bag" });
78+
const inFlightCount = Number(
79+
fetcher.formData?.get("quantity") || 0
80+
);
81+
const optimisticCount = count + inFlightCount;
82+
return (
83+
<>
84+
<BagIcon />
85+
<span>{optimisticCount}</span>
86+
</>
87+
);
88+
}
89+
```
90+
91+
## Components
92+
93+
### `fetcher.Form`
7294

7395
Just like `<Form>` except it doesn't cause a navigation. <small>(You'll get over the dot in JSX ... we hope!)</small>
7496

@@ -83,6 +105,8 @@ function SomeComponent() {
83105
}
84106
```
85107

108+
## Methods
109+
86110
## `fetcher.load()`
87111

88112
Loads data from a route loader.
@@ -140,7 +164,17 @@ If you want to submit to an index route, use the [`?index` param][indexsearchpar
140164

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

143-
## `fetcher.data`
167+
## Properties
168+
169+
### `fetcher.state`
170+
171+
You can know the state of the fetcher with `fetcher.state`. It will be one of:
172+
173+
- **idle** - nothing is being fetched.
174+
- **submitting** - A route action is being called due to a fetcher submission using POST, PUT, PATCH, or DELETE
175+
- **loading** - The fetcher is calling a loader (from a `fetcher.load`) or is being revalidated after a separate submission or `useRevalidator` call
176+
177+
### `fetcher.data`
144178

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

@@ -171,7 +205,7 @@ function ProductDetails({ product }) {
171205
}
172206
```
173207

174-
## `fetcher.formData`
208+
### `fetcher.formData`
175209

176210
When using `<fetcher.Form>` or `fetcher.submit()`, the form data is available to build optimistic UI.
177211

@@ -204,15 +238,15 @@ function TaskCheckbox({ task }) {
204238
}
205239
```
206240

207-
## `fetcher.json`
241+
### `fetcher.json`
208242

209243
When using `fetcher.submit(data, { formEncType: "application/json" })`, the submitted JSON is available via `fetcher.json`.
210244

211-
## `fetcher.text`
245+
### `fetcher.text`
212246

213247
When using `fetcher.submit(data, { formEncType: "text/plain" })`, the submitted text is available via `fetcher.text`.
214248

215-
## `fetcher.formAction`
249+
### `fetcher.formAction`
216250

217251
Tells you the action url the form is being submitted to.
218252

@@ -223,7 +257,7 @@ Tells you the action url the form is being submitted to.
223257
fetcher.formAction; // "mark-as-read"
224258
```
225259

226-
## `fetcher.formMethod`
260+
### `fetcher.formMethod`
227261

228262
Tells you the method of the form being submitted: get, post, put, patch, or delete.
229263

docs/hooks/use-submit.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,15 @@ submit(null, {
150150
<Form action="/logout" method="post" />;
151151
```
152152

153-
Because submissions are navigations, the options may also contain the other navigation related props from [`<Form>`][form] such as `replace`, `state`, `preventScrollReset`, `relative`, `unstable_viewTransition` etc.
153+
Because submissions are navigations, the options may also contain the other navigation related props from [`<Form>`][form] such as:
154+
155+
- `fetcherKey`
156+
- `navigate`
157+
- `preventScrollReset`
158+
- `relative`
159+
- `replace`
160+
- `state`
161+
- `unstable_viewTransition`
154162

155163
[pickingarouter]: ../routers/picking-a-router
156164
[form]: ../components/form

0 commit comments

Comments
 (0)