Skip to content
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

Stabilize future.unstable_skipActionErrorRevalidation #11769

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/gold-snakes-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@remix-run/router": minor
---

Stabilize `future.unstable_skipActionErrorRevalidation` as `future.v7_skipActionErrorRevalidation`

- When this flag is enabled, actions will not automatically trigger a revalidation if they return/throw a `Response` with a `4xx`/`5xx` status code
- You may still opt-into revalidation via `shouldRevalidate`
- This also changes `shouldRevalidate`'s `unstable_actionStatus` parameter to `actionStatus`
6 changes: 3 additions & 3 deletions docs/route/should-revalidate.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface ShouldRevalidateFunctionArgs {
formData?: Submission["formData"];
json?: Submission["json"];
actionResult?: any;
unstable_actionStatus?: number;
actionStatus?: number;
defaultShouldRevalidate: boolean;
}
```
Expand All @@ -40,8 +40,8 @@ There are several instances where data is revalidated, keeping your UI in sync w

- After an [`action`][action] is called via:
- [`<Form>`][form], [`<fetcher.Form>`][fetcher], [`useSubmit`][usesubmit], or [`fetcher.submit`][fetcher]
- When the `future.unstable_skipActionErrorRevalidation` flag is enabled, `loaders` will not revalidate by default if the `action` returns or throws a 4xx/5xx `Response`
- You can opt-into revalidation for these scenarios via `shouldRevalidate` and the `unstable_actionStatus` parameter
- When the `future.v7_skipActionErrorRevalidation` flag is enabled, `loaders` will not revalidate by default if the `action` returns or throws a 4xx/5xx `Response`
- You can opt-into revalidation for these scenarios via `shouldRevalidate` and the `actionStatus` parameter
- When an explicit revalidation is triggered via [`useRevalidator`][userevalidator]
- When the [URL params][params] change for an already rendered route
- When the URL Search params change
Expand Down
4 changes: 2 additions & 2 deletions docs/routers/create-browser-router.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ The following future flags are currently available:
| `v7_partialHydration` | Support partial hydration for Server-rendered apps |
| `v7_prependBasename` | Prepend the router basename to navigate/fetch paths |
| [`v7_relativeSplatPath`][relativesplatpath] | Fix buggy relative path resolution in splat routes |
| `unstable_skipActionErrorRevalidation` | Do not revalidate by default if the action returns a 4xx/5xx `Response` |
| `v7_skipActionErrorRevalidation` | Do not revalidate by default if the action returns a 4xx/5xx `Response` |

## `opts.hydrationData`

Expand Down Expand Up @@ -246,7 +246,7 @@ interface HandlerResult {
- 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`
- After the `action`, `dataStrategy` will be called again for the `loader` revalidation, and all matches will have `shouldLoad=true` (assuming no custom `shouldRevalidate` implementations)

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

### Example Use Cases

Expand Down
67 changes: 67 additions & 0 deletions docs/upgrading/future.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,70 @@ createBrowserRouter(routes, {
},
});
```

## v7_skipActionStatusRevalidation
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to copy this section to the docs/upgrading/v6.md file in the v7 branch


<docs-warning>If you are not using a `createBrowserRouter` you can skip this</docs-warning>

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.

👉 **Enable the Flag**

```tsx
createBrowserRouter(routes, {
future: {
v7_skipActionStatusRevalidation: true,
},
});
```

**Update your Code**

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:

👉 **Option 1: Change the `action` to avoid mutations in error scenarios**

```js
// Before
async function action() {
await mutateSomeData();
if (detectError()) {
throw new Response(error, { status: 400 });
}
await mutateOtherData();
// ...
}

// After
async function action() {
if (detectError()) {
throw new Response(error, { status: 400 });
}
// All data is now mutated after validations
await mutateSomeData();
await mutateOtherData();
// ...
}
```

👉 **Option 2: Opt-into revalidation via `shouldRevalidate` and `actionStatus`**

```js
async function action() {
await mutateSomeData();
if (detectError()) {
throw new Response(error, { status: 400 });
}
await mutateOtherData();
}

async function loader() { ... }

function shouldRevalidate({ actionStatus, defaultShouldRevalidate }) {
if (actionStatus != null && actionStatus >= 400) {
// Revalidate this loader when actions return a 4xx/5xx status
return true;
}
return defaultShouldRevalidate;
}
```
2 changes: 1 addition & 1 deletion packages/react-router-dom/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export function createStaticRouter(
v7_partialHydration: opts.future?.v7_partialHydration === true,
v7_prependBasename: false,
v7_relativeSplatPath: opts.future?.v7_relativeSplatPath === true,
unstable_skipActionErrorRevalidation: false,
v7_skipActionErrorRevalidation: false,
};
},
get state() {
Expand Down
2 changes: 1 addition & 1 deletion packages/router/__tests__/fetchers-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2106,6 +2106,7 @@ describe("fetchers", () => {
expect(shouldRevalidate.mock.calls[0][0]).toMatchInlineSnapshot(`
{
"actionResult": null,
"actionStatus": undefined,
"currentParams": {
"a": "one",
},
Expand All @@ -2122,7 +2123,6 @@ describe("fetchers", () => {
},
"nextUrl": "http://localhost/two/three",
"text": undefined,
"unstable_actionStatus": undefined,
}
`);

Expand Down
10 changes: 5 additions & 5 deletions packages/router/__tests__/should-revalidate-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ describe("shouldRevalidate", () => {
formAction: "/child",
formEncType: "application/x-www-form-urlencoded",
actionResult: "ACTION",
unstable_actionStatus: 201,
actionStatus: 201,
};
expect(arg).toMatchObject(expectedArg);
// @ts-expect-error
Expand Down Expand Up @@ -709,6 +709,7 @@ describe("shouldRevalidate", () => {
expect(arg).toMatchInlineSnapshot(`
{
"actionResult": "FETCH",
"actionStatus": undefined,
"currentParams": {},
"currentUrl": "http://localhost/",
"defaultShouldRevalidate": true,
Expand All @@ -720,7 +721,6 @@ describe("shouldRevalidate", () => {
"nextParams": {},
"nextUrl": "http://localhost/",
"text": undefined,
"unstable_actionStatus": undefined,
}
`);
expect(Object.fromEntries(arg.formData)).toEqual({ key: "value" });
Expand Down Expand Up @@ -773,6 +773,7 @@ describe("shouldRevalidate", () => {
expect(arg).toMatchInlineSnapshot(`
{
"actionResult": undefined,
"actionStatus": undefined,
"currentParams": {},
"currentUrl": "http://localhost/",
"defaultShouldRevalidate": true,
Expand All @@ -784,7 +785,6 @@ describe("shouldRevalidate", () => {
"nextParams": {},
"nextUrl": "http://localhost/",
"text": undefined,
"unstable_actionStatus": undefined,
}
`);

Expand Down Expand Up @@ -1214,7 +1214,7 @@ describe("shouldRevalidate", () => {
root: "ROOT",
},
},
future: { unstable_skipActionErrorRevalidation: true },
future: { v7_skipActionErrorRevalidation: true },
});
router.initialize();

Expand Down Expand Up @@ -1282,7 +1282,7 @@ describe("shouldRevalidate", () => {
root: "ROOT",
},
},
future: { unstable_skipActionErrorRevalidation: true },
future: { v7_skipActionErrorRevalidation: true },
});
router.initialize();

Expand Down
12 changes: 6 additions & 6 deletions packages/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ export interface FutureConfig {
v7_partialHydration: boolean;
v7_prependBasename: boolean;
v7_relativeSplatPath: boolean;
unstable_skipActionErrorRevalidation: boolean;
v7_skipActionErrorRevalidation: boolean;
}

/**
Expand Down Expand Up @@ -806,7 +806,7 @@ export function createRouter(init: RouterInit): Router {
v7_partialHydration: false,
v7_prependBasename: false,
v7_relativeSplatPath: false,
unstable_skipActionErrorRevalidation: false,
v7_skipActionErrorRevalidation: false,
...init.future,
};
// Cleanup function for history
Expand Down Expand Up @@ -1891,7 +1891,7 @@ export function createRouter(init: RouterInit): Router {
activeSubmission,
location,
future.v7_partialHydration && initialHydration === true,
future.unstable_skipActionErrorRevalidation,
future.v7_skipActionErrorRevalidation,
isRevalidationRequired,
cancelledDeferredRoutes,
cancelledFetcherLoads,
Expand Down Expand Up @@ -2350,7 +2350,7 @@ export function createRouter(init: RouterInit): Router {
submission,
nextLocation,
false,
future.unstable_skipActionErrorRevalidation,
future.v7_skipActionErrorRevalidation,
isRevalidationRequired,
cancelledDeferredRoutes,
cancelledFetcherLoads,
Expand Down Expand Up @@ -4384,7 +4384,7 @@ function getMatchesToLoad(
nextParams: nextRouteMatch.params,
...submission,
actionResult,
unstable_actionStatus: actionStatus,
actionStatus,
defaultShouldRevalidate: shouldSkipRevalidation
? false
: // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
Expand Down Expand Up @@ -4463,7 +4463,7 @@ function getMatchesToLoad(
nextParams: matches[matches.length - 1].params,
...submission,
actionResult,
unstable_actionStatus: actionStatus,
actionStatus,
defaultShouldRevalidate: shouldSkipRevalidation
? false
: isRevalidationRequired,
Expand Down
2 changes: 1 addition & 1 deletion packages/router/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export interface ShouldRevalidateFunctionArgs {
text?: Submission["text"];
formData?: Submission["formData"];
json?: Submission["json"];
unstable_actionStatus?: number;
actionStatus?: number;
actionResult?: any;
defaultShouldRevalidate: boolean;
}
Expand Down