react-router
Version:
Declarative routing for React
186 lines (150 loc) • 5.61 kB
Markdown
---
title: Revalidation Optimization
---
# Revalidation Optimization
[MODES: framework, data]
<br/>
<br/>
After a mutation or some navigations, React Router re-runs loaders so
the UI stays in sync with the server. That default is the right
starting point. When a loader is expensive, or a mutation cannot
affect that route's data, you can skip the reload.
<docs-warning>
Skipping revalidation can leave the UI out of sync with the server.
Prefer targeting a specific action or navigation, and fall back to
`defaultShouldRevalidate` instead of always returning `false`.
</docs-warning>
## Default behavior
The default behavior differs between Framework and Data Modes:
- **Framework Mode with SSR**
- Defaults to opt-out behavior - active loaders are revalidated on navigations and successful submissions ([`Link`][link], [`Form`][form], [`fetcher.submit`](fetcher-submit))
- Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
- **Framework "SPA Mode" and Data Mode**
- Defaults to opt-out behavior on successful submissions - active loaders are revalidated on successful submissions ([`Form`][form], [`fetcher.submit`])
- Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
- Defaults to opt-in behavior for GET navigations ([`Link`][link]) - active loaders are only revalidated if their dynamic params changed, or if any search params changed
- A GET navigation to the _exact_ same URL is treated like a page refresh and all loaders are revalidated.
Matched matched routes are handled independently - A child that skips
revalidation does not skip any ancestor routes.
[`fetcher.load`][use-fetcher] only revalidates by default after action
submissions and explicit [`useRevalidator`][use-revalidator] calls, not
on search-param or param-driven navigations.
A plain `fetch()` to a [resource route][resource-routes] does not
go through the router, so it does not revalidate loaders.
## Skip a route with `shouldRevalidate`
Export `shouldRevalidate` from the [route module][route-module]
(Framework Mode) or set it on the [route object][data-mode]
(Data Mode). Returning `false` skips **that route's** loader.
```tsx filename=app/routes/dashboard.tsx
// Framework Mode
export function shouldRevalidate() {
return false;
}
```
```tsx src/main.tsx
// Data Mode
createBrowserRouter([
{
path: "/dashboard",
loader: dashboardLoader,
shouldRevalidate: () => false,
Component: Dashboard,
},
]);
```
Always returning `false` opts that route out of the default
behavior completely, including cases you usually still want
(param changes, explicit [`useRevalidator`][use-revalidator]).
Prefer the conditional form below.
## Opt out of specific requests
Inspect
[`ShouldRevalidateFunctionArgs`][should-revalidate-args]
and return `defaultShouldRevalidate` for everything else.
```tsx
import type { ShouldRevalidateFunctionArgs } from "react-router";
export function shouldRevalidate({
formMethod,
formAction,
defaultShouldRevalidate,
}: ShouldRevalidateFunctionArgs) {
if (
formMethod === "POST" &&
formAction?.endsWith("/analytics")
) {
return false;
}
return defaultShouldRevalidate;
}
```
Other useful fields:
- `formData`, `json`, `text` — the submission body
- `actionResult`, `actionStatus` — the action's return value
- `currentUrl`, `nextUrl`, `currentParams`, `nextParams` —
the navigation
You can ignore search-param-only updates while still
revalidating when the pathname changes:
```tsx
export function shouldRevalidate({
currentUrl,
nextUrl,
defaultShouldRevalidate,
}: ShouldRevalidateFunctionArgs) {
if (currentUrl.pathname === nextUrl.pathname) {
return false;
}
return defaultShouldRevalidate;
}
```
## Skip revalidation for one event
Pass `defaultShouldRevalidate={false}` at the call site so you
do not have to change every route file. This works on
[`<Form>`][form], [`<Link>`][link], `<fetcher.Form>`, and as an
option to [`useSubmit`][use-submit], `fetcher.submit`,
[`useNavigate`][use-navigate], and
[`useSearchParams`][use-search-params].
```tsx
import { Form, Link } from "react-router";
<Link
to="/search?q=shoes"
defaultShouldRevalidate={false}
>
Search Shoes
</Link>
<Form
method="post"
action="/analytics"
defaultShouldRevalidate={false}
>
<button>Track Click</button>
</Form>
```
```tsx
fetcher.submit(
{ intent: "save-progress" },
{
method: "post",
action: "/save-progress",
defaultShouldRevalidate: false,
},
);
```
If a matched route does **not** export `shouldRevalidate`, this
value is used directly for that loader. If it **does** export
`shouldRevalidate`, the value is passed in as
`defaultShouldRevalidate` and the route still has the final say.
That is why a child `shouldRevalidate` that always returns
`false` cannot hide a root reload after `fetcher.submit`. Either
also opt `root` out for that case, or pass
`defaultShouldRevalidate: false` at the call site when `root`
has no `shouldRevalidate` of its own.
[data-mode]: ../start/data/route-object#shouldrevalidate
[form]: ../api/components/Form
[link]: ../api/components/Link
[resource-routes]: ./resource-routes
[route-module]: ../start/framework/route-module#shouldrevalidate
[should-revalidate-args]: https://api.reactrouter.com/v8/interfaces/react-router.ShouldRevalidateFunctionArgs.html
[use-fetcher]: ../api/hooks/useFetcher
[use-navigate]: ../api/hooks/useNavigate
[use-revalidator]: ../api/hooks/useRevalidator
[use-search-params]: ../api/hooks/useSearchParams
[use-submit]: ../api/hooks/useSubmit