Skip to content

Commit 7b22365

Browse files
authored
Stabilize future.unstable_skipActionErrorRevalidation (#11769)
1 parent 9e1da44 commit 7b22365

File tree

9 files changed

+95
-19
lines changed

9 files changed

+95
-19
lines changed

.changeset/gold-snakes-build.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@remix-run/router": minor
3+
---
4+
5+
Stabilize `future.unstable_skipActionErrorRevalidation` as `future.v7_skipActionErrorRevalidation`
6+
7+
- When this flag is enabled, actions will not automatically trigger a revalidation if they return/throw a `Response` with a `4xx`/`5xx` status code
8+
- You may still opt-into revalidation via `shouldRevalidate`
9+
- This also changes `shouldRevalidate`'s `unstable_actionStatus` parameter to `actionStatus`

docs/route/should-revalidate.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ interface ShouldRevalidateFunctionArgs {
2525
formData?: Submission["formData"];
2626
json?: Submission["json"];
2727
actionResult?: any;
28-
unstable_actionStatus?: number;
28+
actionStatus?: number;
2929
defaultShouldRevalidate: boolean;
3030
}
3131
```
@@ -40,8 +40,8 @@ There are several instances where data is revalidated, keeping your UI in sync w
4040

4141
- After an [`action`][action] is called via:
4242
- [`<Form>`][form], [`<fetcher.Form>`][fetcher], [`useSubmit`][usesubmit], or [`fetcher.submit`][fetcher]
43-
- When the `future.unstable_skipActionErrorRevalidation` flag is enabled, `loaders` will not revalidate by default if the `action` returns or throws a 4xx/5xx `Response`
44-
- You can opt-into revalidation for these scenarios via `shouldRevalidate` and the `unstable_actionStatus` parameter
43+
- When the `future.v7_skipActionErrorRevalidation` flag is enabled, `loaders` will not revalidate by default if the `action` returns or throws a 4xx/5xx `Response`
44+
- You can opt-into revalidation for these scenarios via `shouldRevalidate` and the `actionStatus` parameter
4545
- When an explicit revalidation is triggered via [`useRevalidator`][userevalidator]
4646
- When the [URL params][params] change for an already rendered route
4747
- When the URL Search params change

docs/routers/create-browser-router.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ The following future flags are currently available:
125125
| `v7_partialHydration` | Support partial hydration for Server-rendered apps |
126126
| `v7_prependBasename` | Prepend the router basename to navigate/fetch paths |
127127
| [`v7_relativeSplatPath`][relativesplatpath] | Fix buggy relative path resolution in splat routes |
128-
| `unstable_skipActionErrorRevalidation` | Do not revalidate by default if the action returns a 4xx/5xx `Response` |
128+
| `v7_skipActionErrorRevalidation` | Do not revalidate by default if the action returns a 4xx/5xx `Response` |
129129

130130
## `opts.hydrationData`
131131

@@ -246,7 +246,7 @@ interface HandlerResult {
246246
- If you are on `/parent/child/a` and you submit to `a`'s `action`, then only `a` will have `shouldLoad=true` for the action execution of `dataStrategy`
247247
- After the `action`, `dataStrategy` will be called again for the `loader` revalidation, and all matches will have `shouldLoad=true` (assuming no custom `shouldRevalidate` implementations)
248248

249-
The `dataStrategy` function should return a parallel array of `HandlerResult` instances, which indicates if the handler was successful or not. If the returned `handlerResult.result` is a `Response`, React Router will unwrap it for you (via `res.json` or `res.text`). If you need to do custom decoding of a `Response` but preserve the status code, you can return the decoded value in `handlerResult.result` and send the status along via `handlerResult.status` (for example, when using the `future.unstable_skipActionRevalidation` flag). `match.resolve()` will return a `HandlerResult` if you are not passing it a handler override function. If you are, then you need to wrap the `handler` result in a `HandlerResult` (see examples below).
249+
The `dataStrategy` function should return a parallel array of `HandlerResult` instances, which indicates if the handler was successful or not. If the returned `handlerResult.result` is a `Response`, React Router will unwrap it for you (via `res.json` or `res.text`). If you need to do custom decoding of a `Response` but preserve the status code, you can return the decoded value in `handlerResult.result` and send the status along via `handlerResult.status` (for example, when using the `future.v7_skipActionRevalidation` flag). `match.resolve()` will return a `HandlerResult` if you are not passing it a handler override function. If you are, then you need to wrap the `handler` result in a `HandlerResult` (see examples below).
250250

251251
### Example Use Cases
252252

docs/upgrading/future.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,70 @@ createBrowserRouter(routes, {
199199
},
200200
});
201201
```
202+
203+
## v7_skipActionStatusRevalidation
204+
205+
<docs-warning>If you are not using a `createBrowserRouter` you can skip this</docs-warning>
206+
207+
When this flag is enabled, loaders will no longer revalidate by default after an action throws/returns a `Response` with a `4xx`/`5xx` status code. You may opt-into revalidation in these scenarios via `shouldRevalidate` and the `actionStatus` parameter.
208+
209+
👉 **Enable the Flag**
210+
211+
```tsx
212+
createBrowserRouter(routes, {
213+
future: {
214+
v7_skipActionStatusRevalidation: true,
215+
},
216+
});
217+
```
218+
219+
**Update your Code**
220+
221+
In most cases, you probably won't have to make changes to your app code. Usually, if an action errors, it's unlikely data was mutated and needs revalidation. If any of your code _does_ mutate data in action error scenarios you have 2 options:
222+
223+
👉 **Option 1: Change the `action` to avoid mutations in error scenarios**
224+
225+
```js
226+
// Before
227+
async function action() {
228+
await mutateSomeData();
229+
if (detectError()) {
230+
throw new Response(error, { status: 400 });
231+
}
232+
await mutateOtherData();
233+
// ...
234+
}
235+
236+
// After
237+
async function action() {
238+
if (detectError()) {
239+
throw new Response(error, { status: 400 });
240+
}
241+
// All data is now mutated after validations
242+
await mutateSomeData();
243+
await mutateOtherData();
244+
// ...
245+
}
246+
```
247+
248+
👉 **Option 2: Opt-into revalidation via `shouldRevalidate` and `actionStatus`**
249+
250+
```js
251+
async function action() {
252+
await mutateSomeData();
253+
if (detectError()) {
254+
throw new Response(error, { status: 400 });
255+
}
256+
await mutateOtherData();
257+
}
258+
259+
async function loader() { ... }
260+
261+
function shouldRevalidate({ actionStatus, defaultShouldRevalidate }) {
262+
if (actionStatus != null && actionStatus >= 400) {
263+
// Revalidate this loader when actions return a 4xx/5xx status
264+
return true;
265+
}
266+
return defaultShouldRevalidate;
267+
}
268+
```

packages/react-router-dom/server.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ export function createStaticRouter(
315315
v7_partialHydration: opts.future?.v7_partialHydration === true,
316316
v7_prependBasename: false,
317317
v7_relativeSplatPath: opts.future?.v7_relativeSplatPath === true,
318-
unstable_skipActionErrorRevalidation: false,
318+
v7_skipActionErrorRevalidation: false,
319319
};
320320
},
321321
get state() {

packages/router/__tests__/fetchers-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2106,6 +2106,7 @@ describe("fetchers", () => {
21062106
expect(shouldRevalidate.mock.calls[0][0]).toMatchInlineSnapshot(`
21072107
{
21082108
"actionResult": null,
2109+
"actionStatus": undefined,
21092110
"currentParams": {
21102111
"a": "one",
21112112
},
@@ -2122,7 +2123,6 @@ describe("fetchers", () => {
21222123
},
21232124
"nextUrl": "http://localhost/two/three",
21242125
"text": undefined,
2125-
"unstable_actionStatus": undefined,
21262126
}
21272127
`);
21282128

packages/router/__tests__/should-revalidate-test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ describe("shouldRevalidate", () => {
368368
formAction: "/child",
369369
formEncType: "application/x-www-form-urlencoded",
370370
actionResult: "ACTION",
371-
unstable_actionStatus: 201,
371+
actionStatus: 201,
372372
};
373373
expect(arg).toMatchObject(expectedArg);
374374
// @ts-expect-error
@@ -709,6 +709,7 @@ describe("shouldRevalidate", () => {
709709
expect(arg).toMatchInlineSnapshot(`
710710
{
711711
"actionResult": "FETCH",
712+
"actionStatus": undefined,
712713
"currentParams": {},
713714
"currentUrl": "http://localhost/",
714715
"defaultShouldRevalidate": true,
@@ -720,7 +721,6 @@ describe("shouldRevalidate", () => {
720721
"nextParams": {},
721722
"nextUrl": "http://localhost/",
722723
"text": undefined,
723-
"unstable_actionStatus": undefined,
724724
}
725725
`);
726726
expect(Object.fromEntries(arg.formData)).toEqual({ key: "value" });
@@ -773,6 +773,7 @@ describe("shouldRevalidate", () => {
773773
expect(arg).toMatchInlineSnapshot(`
774774
{
775775
"actionResult": undefined,
776+
"actionStatus": undefined,
776777
"currentParams": {},
777778
"currentUrl": "http://localhost/",
778779
"defaultShouldRevalidate": true,
@@ -784,7 +785,6 @@ describe("shouldRevalidate", () => {
784785
"nextParams": {},
785786
"nextUrl": "http://localhost/",
786787
"text": undefined,
787-
"unstable_actionStatus": undefined,
788788
}
789789
`);
790790

@@ -1214,7 +1214,7 @@ describe("shouldRevalidate", () => {
12141214
root: "ROOT",
12151215
},
12161216
},
1217-
future: { unstable_skipActionErrorRevalidation: true },
1217+
future: { v7_skipActionErrorRevalidation: true },
12181218
});
12191219
router.initialize();
12201220

@@ -1282,7 +1282,7 @@ describe("shouldRevalidate", () => {
12821282
root: "ROOT",
12831283
},
12841284
},
1285-
future: { unstable_skipActionErrorRevalidation: true },
1285+
future: { v7_skipActionErrorRevalidation: true },
12861286
});
12871287
router.initialize();
12881288

packages/router/router.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ export interface FutureConfig {
372372
v7_partialHydration: boolean;
373373
v7_prependBasename: boolean;
374374
v7_relativeSplatPath: boolean;
375-
unstable_skipActionErrorRevalidation: boolean;
375+
v7_skipActionErrorRevalidation: boolean;
376376
}
377377

378378
/**
@@ -806,7 +806,7 @@ export function createRouter(init: RouterInit): Router {
806806
v7_partialHydration: false,
807807
v7_prependBasename: false,
808808
v7_relativeSplatPath: false,
809-
unstable_skipActionErrorRevalidation: false,
809+
v7_skipActionErrorRevalidation: false,
810810
...init.future,
811811
};
812812
// Cleanup function for history
@@ -1894,7 +1894,7 @@ export function createRouter(init: RouterInit): Router {
18941894
activeSubmission,
18951895
location,
18961896
future.v7_partialHydration && initialHydration === true,
1897-
future.unstable_skipActionErrorRevalidation,
1897+
future.v7_skipActionErrorRevalidation,
18981898
isRevalidationRequired,
18991899
cancelledDeferredRoutes,
19001900
cancelledFetcherLoads,
@@ -2353,7 +2353,7 @@ export function createRouter(init: RouterInit): Router {
23532353
submission,
23542354
nextLocation,
23552355
false,
2356-
future.unstable_skipActionErrorRevalidation,
2356+
future.v7_skipActionErrorRevalidation,
23572357
isRevalidationRequired,
23582358
cancelledDeferredRoutes,
23592359
cancelledFetcherLoads,
@@ -4386,7 +4386,7 @@ function getMatchesToLoad(
43864386
nextParams: nextRouteMatch.params,
43874387
...submission,
43884388
actionResult,
4389-
unstable_actionStatus: actionStatus,
4389+
actionStatus,
43904390
defaultShouldRevalidate: shouldSkipRevalidation
43914391
? false
43924392
: // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
@@ -4465,7 +4465,7 @@ function getMatchesToLoad(
44654465
nextParams: matches[matches.length - 1].params,
44664466
...submission,
44674467
actionResult,
4468-
unstable_actionStatus: actionStatus,
4468+
actionStatus,
44694469
defaultShouldRevalidate: shouldSkipRevalidation
44704470
? false
44714471
: isRevalidationRequired,

packages/router/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export interface ShouldRevalidateFunctionArgs {
210210
text?: Submission["text"];
211211
formData?: Submission["formData"];
212212
json?: Submission["json"];
213-
unstable_actionStatus?: number;
213+
actionStatus?: number;
214214
actionResult?: any;
215215
defaultShouldRevalidate: boolean;
216216
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy