Skip to content

Commit

Permalink
Update/fix Single Fetch revalidation behavior (#9938)
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 authored Sep 5, 2024
1 parent dc3c3e9 commit 5cfd3c3
Show file tree
Hide file tree
Showing 14 changed files with 1,458 additions and 184 deletions.
14 changes: 14 additions & 0 deletions .changeset/flat-wasps-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@remix-run/react": patch
"@remix-run/server-runtime": patch
---

Single Fetch - fix revalidation behavior bugs

- With Single Fetch, existing routes revalidate by default
- This means requests do not need special query params for granular route revalidations out of the box - i.e., `GET /a/b/c.data`
- There are two conditions that will trigger granular revalidation:
- If a route opts out of revalidation via `shouldRevalidate`, it will be excluded from the single fetch call
- If a route defines a `clientLoader` then it will be excluded from the single fetch call and if you call `serverLoader()` from your `clientLoader`, that will make a separarte HTTP call for just that route loader - i.e., `GET /a/b/c.data?_routes=routes/a` for a `clientLoader` in `routes/a.tsx`
- When one or more routes are excluded from the single fetch call, the remaining routes that have loaders are included as query params:
- For example, if A was excluded, and the `root` route and `routes/b` had a `loader` but `routes/c` did not, the single fetch request would be `GET /a/b/c.data?_routes=root,routes/a`
34 changes: 33 additions & 1 deletion docs/guides/single-fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ There are a handful of breaking changes introduced with Single Fetch - some of w
- Add `@remix-run/react/future/single-fetch.d.ts` to the end of your `tsconfig.json`'s `compilerOptions.types` array
- Begin using `unstable_defineLoader`/`unstable_defineAction` in your routes
- This can be done incrementally - you should have _mostly_ accurate type inference in your current state
- [**Default revalidation behavior changes to opt-out on GET navigations**][revalidation]: Default revalidation behavior on normal navigations changes from opt-in to opt-out and your server loaders will re-run by default
- [**Opt-in `action` revalidation**][action-revalidation]: Revalidation after an `action` `4xx`/`5xx` `Response` is now opt-in, versus opt-out

## Adding a New Route with Single Fetch
Expand Down Expand Up @@ -427,6 +428,32 @@ function handleBrowserRequest(

### Revalidations

#### Normal Navigation Behavior

In addition to the simpler mental model and the alignment of document and data requests, another benefit of Single Fetch is simpler (and hopefully better) caching behavior. Generally, Single Fetch will make fewer HTTP requests and hopefully cache those results more frequently compared to the previous multiple-fetch behavior.

To reduce cache fragmentation, Single Fetch changes the default revalidation behavior on GET navigations. Previously, Remix would not re-run loaders for reused ancestor routes unless you opted-in via `shouldRevalidate`. Now, Remix _will_ re-run those by default in the simple case for a Single Fetch request like `GET /a/b/c.data`. If you do not have any `shouldRevalidate` or `clientLoader` functions, this will be the behavior for your app.

Adding either a `shouldRevalidate` or a `clientLoader` to any of the active routes will trigger granular Single Fetch calls that include a `_routes` parameter specifying the subset of routes to run.

If a `clientLoader` calls `serverLoader()` internally, that will trigger a separate HTTP call for that specific route, akin to the old behavior.

For example, if you are on `/a/b` and you navigate to `/a/b/c`:

- When no `shouldRevalidate` or `clientLoader` functions exist: `GET /a/b/c.data`
- If all routes have loaders but `routes/a` opts out via `shouldRevalidate`:
- `GET /a/b/c.data?_routes=root,routes/b,routes/c`
- If all routes have loaders but `routes/b` has a `clientLoader`:
- `GET /a/b/c.data?_routes=root,routes/a,routes/c`
- And then if B's `clientLoader` calls `serverLoader()`:
- `GET /a/b/c.data?_routes=routes/b`

If this new behavior is sub-optimal for your application, you should be able to opt-back into the old behavior of not-revalidating by adding a `shouldRevalidate` that returns `false` in the desired scenarios to your parent routes.

Another option is to leverage a server-side cache for expensive parent loader calculations.

#### Submission Revalidation Behavior

Previously, Remix would always revalidate all active loaders after _any_ action submission, regardless of the result of the action. You could opt-out of revalidation on a per-route basis via [`shouldRevalidate`][should-revalidate].

With Single Fetch, if an `action` returns or throws a `Response` with a `4xx/5xx` status code, Remix will _not revalidate_ loaders by default. If an `action` returns or throws anything that is not a 4xx/5xx Response, then the revalidation behavior is unchanged. The reasoning here is that in most cases, if you return a `4xx`/`5xx` Response, you didn't actually mutate any data so there is no need to reload data.
Expand Down Expand Up @@ -458,9 +485,14 @@ Revalidation is handled via a `?_routes` query string parameter on the single fe
[merging-remix-and-rr]: https://remix.run/blog/merging-remix-and-react-router
[migration-guide]: #migrating-a-route-with-single-fetch
[breaking-changes]: #breaking-changes
[action-revalidation]: #streaming-data-format
[revalidation]: #normal-navigation-behavior
[action-revalidation]: #submission-revalidation-behavior
[start]: #enabling-single-fetch
[type-inference-section]: #type-inference
[compatibility-flag]: https://developers.cloudflare.com/workers/configuration/compatibility-dates
[data-utility]: ../utils/data
[augment]: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation

```
```
2 changes: 1 addition & 1 deletion integration/error-boundary-v2-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ test.describe("single fetch", () => {
await waitForAndAssert(
page,
app,
"#child-error",
"#parent-error",
"Unable to decode turbo-stream response from URL"
);
});
Expand Down
2 changes: 1 addition & 1 deletion integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@remix-run/dev": "workspace:*",
"@remix-run/express": "workspace:*",
"@remix-run/node": "workspace:*",
"@remix-run/router": "0.0.0-experimental-7d87ffb8c",
"@remix-run/router": "1.19.2-pre.0",
"@remix-run/server-runtime": "workspace:*",
"@types/express": "^4.17.9",
"@vanilla-extract/css": "^1.10.0",
Expand Down
Loading

0 comments on commit 5cfd3c3

Please sign in to comment.