How to Manually Trigger Route Revalidation in React Router
Use the useRevalidator hook inside components or call router.revalidate() directly on the router instance to force loaders to re-run without navigating.
React Router's data routers automatically revalidate loaders after form submissions and navigation events, but sometimes you need to manually trigger route revalidation in React Router when external data changes. Whether you're responding to WebSocket messages, window focus events, or custom refresh buttons, the library provides two primary APIs to force loader re-execution while staying on the current page.
Understanding Automatic vs. Manual Revalidation
By default, React Router revalidates route loaders after actions complete or when the user navigates to a new route. However, real-world applications often need to refresh data in response to events outside this standard lifecycle—such as receiving a push notification, detecting a window refocus, or polling a backend status endpoint. Manual revalidation bridges this gap without requiring a full page reload or artificial navigation.
Method 1: Using the useRevalidator Hook
The useRevalidator hook is the idiomatic way to trigger revalidation from within React components. It returns a Revalidator object containing a revalidate function and a state property indicating whether revalidation is in progress.
Implementation Details
According to the React Router source code, useRevalidator is implemented in packages/react-router/lib/hooks.tsx at lines 1503-1516. The hook accesses the DataRouterContext and exposes the router's internal revalidate method through a stable callback reference.
import { useRevalidator } from "react-router";
export function RefreshButton() {
const { revalidate, state } = useRevalidator();
return (
<button
onClick={() => revalidate()}
disabled={state === "loading"}
>
{state === "loading" ? "Refreshing…" : "Refresh Data"}
</button>
);
}
Method 2: Calling router.revalidate() Directly
For scenarios outside of React's component tree—such as service workers, external event listeners, or test utilities—you can call revalidate() directly on the router instance returned by createBrowserRouter, createMemoryRouter, or createHashRouter.
Internal Router Mechanics
The core implementation resides in packages/react-router/lib/router/router.ts at lines 1640-1662. When invoked, router.revalidate() performs several critical operations:
- Creates a deferred promise (
pendingRevalidationDfd) to track completion - Aborts any in-flight loads via
interruptActiveLoads() - Transitions the router state to
"loading" - Initiates an uninterrupted navigation to the current location, re-running all loaders without pushing a new history entry
import { createBrowserRouter } from "react-router";
const router = createBrowserRouter([
{
path: "/",
loader: async () => fetch("/api/dashboard").then(r => r.json()),
element: <Dashboard />,
},
]);
// Trigger from external context (e.g., after WebSocket message)
router.revalidate().then(() => {
console.log("All loaders have re-run");
});
When to Use Manual Revalidation
Choose the appropriate API based on your execution context:
| Scenario | Recommended API |
|---|---|
| Inside React components responding to user interactions | useRevalidator hook |
External event handlers (WebSocket, window events) |
router.revalidate() |
| Service workers or non-React utilities | router.revalidate() |
| Testing environments requiring deterministic loader execution | await router.revalidate() |
Advanced Example: Window Focus Revalidation
A common pattern for single-page applications is refreshing data when the user returns to the tab. This combines useRevalidator with the window focus event:
import { useEffect } from "react";
import { useRevalidator } from "react-router";
export function FocusRevalidator() {
const { revalidate } = useRevalidator();
useEffect(() => {
const handleFocus = () => revalidate();
window.addEventListener("focus", handleFocus);
return () => window.removeEventListener("focus", handleFocus);
}, [revalidate]);
return null;
}
Summary
- Use
useRevalidatorwhen triggering revalidation from React components; it provides reactive state tracking and handles cleanup automatically. - Use
router.revalidate()for imperative control outside React, such as in service workers, tests, or external event listeners. - Both methods ultimately invoke the same internal mechanism in
packages/react-router/lib/router/router.ts, which aborts active loads, sets state to"loading", and re-runs all current route loaders without navigation side effects. - Manual revalidation is essential for keeping UI synchronized with external data sources beyond the standard action-based workflow.
Frequently Asked Questions
How do I know when manual revalidation has completed?
The useRevalidator hook returns a state property that equals "loading" during revalidation and "idle" when finished. If calling router.revalidate() directly, the method returns a Promise<void> that resolves once all loaders have re-run, allowing you to use await router.revalidate() for deterministic completion detection.
Can I revalidate a specific route instead of all loaders?
No. React Router's revalidation API is all-or-nothing for the currently active routes. When you trigger revalidate(), the router re-runs every loader on the current path. To refresh only specific data, consider using React Query, SWR, or other caching libraries alongside React Router's loaders.
Does manual revalidation work with client-side route loaders only?
Manual revalidation works with any loader defined in your route configuration, whether it runs on the client or server (in a Remix context). The router.revalidate() method simply re-invokes the loader functions defined in your route hierarchy, regardless of where those loaders execute.
What happens if I call revalidate during an active navigation?
The router handles this gracefully. According to the implementation in packages/react-router/lib/router/router.ts, calling revalidate() during an active navigation first interrupts any in-flight loads, then either piggybacks on the pending navigation's loader phase or initiates a new uninterrupted navigation to the current location. This ensures you never end up with stale data or race conditions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →