Skip to content

Commit d899050

Browse files
brophdawg11fernandojbf
authored andcommitted
Deprecate fetcher.type and fetcher.submission (remix-run#5691)
1 parent 97696de commit d899050

File tree

5 files changed

+129
-35
lines changed

5 files changed

+129
-35
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/react": patch
3+
---
4+
5+
Deprecate `fetcher.type` and `fetcher.submission` for Remix v2

docs/guides/optimistic-ui.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ Remix can help you build optimistic UI with [`useNavigation`][use-navigation] an
1111
## Strategy
1212

1313
1. User submits a form (or you do with [`useSubmit`][use-submit] or [`fetcher.submit`][fetcher-submission]).
14-
2. Remix makes the submission and its data immediately available to you on [`navigation.formData`][navigation-formdata] or [`fetcher.submission`][fetcher-submission].
15-
3. App uses [`submission.formData`][form-data] to render an optimistic version of _what it will render_ when the submission completes successfully.
14+
2. Remix makes the submission and its data immediately available to you on [`navigation.formData`][navigation-formdata] or [`fetcher.formData`][fetcher-submission].
15+
3. App uses [`formData`][form-data] to render an optimistic version of _what it will render_ when the submission completes successfully.
1616
4. Remix automatically revalidates all the data.
1717
- If successful, the user doesn't even notice.
1818
- If it fails, the page data is automatically in sync with the server so the UI reverts automatically.

docs/hooks/use-fetcher.md

+59-20
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ function SomeComponent() {
4444

4545
// build UI with these
4646
fetcher.state;
47-
fetcher.type;
48-
fetcher.submission;
47+
fetcher.formMethod;
48+
fetcher.formAction;
49+
fetcher.formData;
50+
fetcher.formEncType;
4951
fetcher.data;
5052
}
5153
```
@@ -71,6 +73,8 @@ You can know the state of the fetcher with `fetcher.state`. It will be one of:
7173

7274
#### `fetcher.type`
7375

76+
<docs-error>`fetcher.type` is deprecated and will be removed in v2.</docs-error>
77+
7478
This is the type of state the fetcher is in. It's like `fetcher.state`, but more granular. Depending on the fetcher's state, the types can be the following:
7579

7680
- `state === "idle"`
@@ -89,8 +93,47 @@ This is the type of state the fetcher is in. It's like `fetcher.state`, but more
8993
- **actionRedirect** - The action from an "actionSubmission" returned a redirect and the page is transitioning to the new location.
9094
- **normalLoad** - A route's loader is being called without a submission (`fetcher.load()`).
9195

96+
##### Moving away from `fetcher.type`
97+
98+
The `type` field has been been deprecated and will be removed in v2. We've found that `state` is sufficient for almost all use-cases, and when it's not you can derive sub-types via `fetcher.state` and other fields. Here's a few examples:
99+
100+
```js
101+
function Component() {
102+
let fetcher = useFetcher();
103+
104+
let isDone =
105+
fetcher.state === "idle" && fetcher.data != null;
106+
107+
let isActionSubmission = fetcher.state === "submitting";
108+
109+
let isActionReload =
110+
fetcher.state === "loading" &&
111+
fetcher.formMethod != null &&
112+
fetcher.formMethod != "get" &&
113+
// If we returned data, we must be reloading
114+
fetcher.data != null;
115+
116+
let isActionRedirect =
117+
fetcher.state === "loading" &&
118+
fetcher.formMethod != null &&
119+
navigation.formMethod != "get" &&
120+
// If we have no data we must have redirected
121+
fetcher.data == null;
122+
123+
let isLoaderSubmission =
124+
navigation.state === "loading" &&
125+
navigation.state.formMethod === "get";
126+
127+
let isNormalLoad =
128+
navigation.state === "loading" &&
129+
navigation.state.formMethod == null;
130+
}
131+
```
132+
92133
#### `fetcher.submission`
93134

135+
<docs-error>`fetcher.submission` is deprecated and will be removed in v2. Instead, the fields inside of `submission` have been flattened onto the `fetcher` itself (`fetcher.formMethod`, `fetcher.formAction`, `fetcher.formData`, `fetcher.formEncType`)</docs-error>
136+
94137
When using `<fetcher.Form>` or `fetcher.submit()`, the form submission is available to build optimistic UI.
95138

96139
It is not available when the fetcher state is "idle" or "loading".
@@ -153,7 +196,7 @@ function SomeComponent() {
153196
const fetcher = useFetcher();
154197

155198
useEffect(() => {
156-
if (fetcher.type === "init") {
199+
if (fetcher.state === "idle" && fetcher.data == null) {
157200
fetcher.load("/some/route");
158201
}
159202
}, [fetcher]);
@@ -204,7 +247,10 @@ function NewsletterSignup() {
204247
const ref = useRef();
205248

206249
useEffect(() => {
207-
if (newsletter.type === "done" && newsletter.data.ok) {
250+
if (
251+
newsletter.state === "idle" &&
252+
newsletter.data?.ok
253+
) {
208254
ref.current.reset();
209255
}
210256
}, [newsletter]);
@@ -225,7 +271,7 @@ function NewsletterSignup() {
225271
</button>
226272
</p>
227273

228-
{newsletter.type === "done" ? (
274+
{newsletter.state === "idle" && newsletter.data ? (
229275
newsletter.data.ok ? (
230276
<p>Thanks for subscribing!</p>
231277
) : newsletter.data.error ? (
@@ -283,18 +329,12 @@ export function NewsletterSignup() {
283329
Form={newsletter.Form}
284330
data={newsletter.data}
285331
state={newsletter.state}
286-
type={newsletter.type}
287332
/>
288333
);
289334
}
290335

291336
// used here and in the route
292-
export function NewsletterForm({
293-
Form,
294-
data,
295-
state,
296-
type,
297-
}) {
337+
export function NewsletterForm({ Form, data, state }) {
298338
// refactor a bit in here, just read from props instead of useFetcher
299339
}
300340
```
@@ -309,12 +349,7 @@ import { NewsletterForm } from "~/NewsletterSignup";
309349
export default function NewsletterSignupRoute() {
310350
const data = useActionData<typeof action>();
311351
return (
312-
<NewsletterForm
313-
Form={Form}
314-
data={data}
315-
state="idle"
316-
type="done"
317-
/>
352+
<NewsletterForm Form={Form} data={data} state="idle" />
318353
);
319354
}
320355
```
@@ -355,7 +390,11 @@ function UserAvatar({ partialUser }) {
355390
const [showDetails, setShowDetails] = useState(false);
356391

357392
useEffect(() => {
358-
if (showDetails && userDetails.type === "init") {
393+
if (
394+
showDetails &&
395+
userDetails.state === "idle" &&
396+
!userDetails.data
397+
) {
359398
userDetails.load(`/users/${user.id}/details`);
360399
}
361400
}, [showDetails, userDetails]);
@@ -367,7 +406,7 @@ function UserAvatar({ partialUser }) {
367406
>
368407
<img src={partialUser.profileImageUrl} />
369408
{showDetails ? (
370-
userDetails.type === "done" ? (
409+
userDetails.state === "idle" && userDetails.data ? (
371410
<UserPopup user={userDetails.data} />
372411
) : (
373412
<UserPopupLoading />

docs/hooks/use-fetchers.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ For example, imagine a UI where the sidebar lists projects, and the main view di
2727
+-----------------+----------------------------┘
2828
```
2929

30-
When the user clicks a checkbox, the submission goes to the action to change the state of the task. Instead of creating a "loading state" we want to create an "optimistic UI" that will **immediately** update the checkbox to appear checked even though the server hasn't processed it yet. In the checkbox component, we can use `fetcher.submission`:
30+
When the user clicks a checkbox, the submission goes to the action to change the state of the task. Instead of creating a "loading state" we want to create an "optimistic UI" that will **immediately** update the checkbox to appear checked even though the server hasn't processed it yet. In the checkbox component, we can use `fetcher.formData`:
3131

3232
```tsx
3333
function Task({ task }) {
3434
const toggle = useFetcher();
35-
const checked = toggle.submission
35+
const checked = toggle.formData
3636
? // use the optimistic version
37-
Boolean(toggle.submission.formData.get("complete"))
37+
Boolean(toggle.formData.get("complete"))
3838
: // use the normal version
3939
task.complete;
4040

@@ -81,7 +81,7 @@ This is where `useFetchers` comes in. Up in the sidebar, we can access all the i
8181
The strategy has three steps:
8282

8383
1. Find the submissions for tasks in a specific project
84-
2. Use the `fetcher.submission.formData` to immediately update the count
84+
2. Use the `fetcher.formData` to immediately update the count
8585
3. Use the normal task's state if it's not inflight
8686

8787
Here's some sample code:
@@ -95,15 +95,15 @@ function ProjectTaskCount({ project }) {
9595
const myFetchers = new Map();
9696
for (const f of fetchers) {
9797
if (
98-
f.submission &&
99-
f.submission.action.startsWith(
98+
f.formAction &&
99+
f.formAction.startsWith(
100100
`/projects/${project.id}/task`
101101
)
102102
) {
103-
const taskId = f.submission.formData.get("id");
103+
const taskId = f.formData.get("id");
104104
myFetchers.set(
105105
parseInt(taskId),
106-
f.submission.formData.get("complete") === "on"
106+
f.formData.get("complete") === "on"
107107
);
108108
}
109109
}

packages/remix-react/components.tsx

+55-5
Original file line numberDiff line numberDiff line change
@@ -1361,17 +1361,19 @@ function convertNavigationToTransition(navigation: Navigation): Transition {
13611361
*/
13621362
export function useFetchers(): Fetcher[] {
13631363
let fetchers = useFetchersRR();
1364-
return fetchers.map((f) =>
1365-
convertRouterFetcherToRemixFetcher({
1364+
return fetchers.map((f) => {
1365+
let fetcher = convertRouterFetcherToRemixFetcher({
13661366
state: f.state,
13671367
data: f.data,
13681368
formMethod: f.formMethod,
13691369
formAction: f.formAction,
13701370
formData: f.formData,
13711371
formEncType: f.formEncType,
13721372
" _hasFetcherDoneAnything ": f[" _hasFetcherDoneAnything "],
1373-
})
1374-
);
1373+
});
1374+
addFetcherDeprecationWarnings(fetcher);
1375+
return fetcher;
1376+
});
13751377
}
13761378

13771379
export type FetcherWithComponents<TData> = Fetcher<TData> & {
@@ -1403,15 +1405,63 @@ export function useFetcher<TData = any>(): FetcherWithComponents<
14031405
formEncType: fetcherRR.formEncType,
14041406
" _hasFetcherDoneAnything ": fetcherRR[" _hasFetcherDoneAnything "],
14051407
});
1406-
return {
1408+
let fetcherWithComponents = {
14071409
...remixFetcher,
14081410
load: fetcherRR.load,
14091411
submit: fetcherRR.submit,
14101412
Form: fetcherRR.Form,
14111413
};
1414+
addFetcherDeprecationWarnings(fetcherWithComponents);
1415+
return fetcherWithComponents;
14121416
}, [fetcherRR]);
14131417
}
14141418

1419+
function addFetcherDeprecationWarnings(fetcher: Fetcher) {
1420+
let type: Fetcher["type"] = fetcher.type;
1421+
Object.defineProperty(fetcher, "type", {
1422+
get() {
1423+
warnOnce(
1424+
false,
1425+
"⚠️ DEPRECATED: The `useFetcher().type` field has been deprecated and " +
1426+
"will be removed in Remix v2. Please update your code to rely on " +
1427+
"`fetcher.state`.\n\nSee https://remix.run/docs/hooks/use-fetcher for " +
1428+
"more information."
1429+
);
1430+
return type;
1431+
},
1432+
set(value: Fetcher["type"]) {
1433+
// Devs should *not* be doing this but we don't want to break their
1434+
// current app if they are
1435+
type = value;
1436+
},
1437+
// These settings should make this behave like a normal object `type` field
1438+
configurable: true,
1439+
enumerable: true,
1440+
});
1441+
1442+
let submission: Fetcher["submission"] = fetcher.submission;
1443+
Object.defineProperty(fetcher, "submission", {
1444+
get() {
1445+
warnOnce(
1446+
false,
1447+
"⚠️ DEPRECATED: The `useFetcher().submission` field has been deprecated and " +
1448+
"will be removed in Remix v2. The submission fields now live directly " +
1449+
"on the fetcher (`fetcher.formData`).\n\n" +
1450+
"See https://remix.run/docs/hooks/use-fetcher for more information."
1451+
);
1452+
return submission;
1453+
},
1454+
set(value: Fetcher["submission"]) {
1455+
// Devs should *not* be doing this but we don't want to break their
1456+
// current app if they are
1457+
submission = value;
1458+
},
1459+
// These settings should make this behave like a normal object `type` field
1460+
configurable: true,
1461+
enumerable: true,
1462+
});
1463+
}
1464+
14151465
function convertRouterFetcherToRemixFetcher(
14161466
fetcherRR: Omit<ReturnType<typeof useFetcherRR>, "load" | "submit" | "Form">
14171467
): Fetcher {

0 commit comments

Comments
 (0)