MENU
useRevalidator
useRevalidator() returns { revalidate, state }. Calling revalidate() re-runs the loaders for every route currently rendered on the page and refreshes their useLoaderData() results – without navigating anywhere: the URL doesn't change and nothing unmounts. "state" is "idle" or "loading", reporting the progress of that revalidation separately from useNavigation's own "state".
React Router already revalidates every active loader automatically after any action runs, so useRevalidator() isn't needed for that case. Reach for it when something changes the data from outside the router's own action flow entirely – a WebSocket push, a polling interval, the window regaining focus, or a plain fetch() call made without going through a <Form>/action – and you want the page to catch up without forcing a navigation.
import React, { useEffect } from "react";
import { createBrowserRouter, useLoaderData, useRevalidator } from "react-router";
import { RouterProvider } from "react-router/dom";
const router = createBrowserRouter([
{
path: "/",
element: <Clock />,
loader: () => fetchServerTime(),
},
]);
export default function UseRevalidatorExample() {
return <RouterProvider router={router} />;
}
function Clock() {
let time = useLoaderData();
let revalidator = useRevalidator();
useEffect(() => {
let id = setInterval(() => revalidator.revalidate(), 5000);
return () => clearInterval(id);
}, [revalidator.revalidate]);
return (
<div>
<h2>Server time: {time}</h2>
<p>{revalidator.state === "loading" ? "Refreshing..." : "Up to date"}</p>
<button onClick={() => revalidator.revalidate()}>Refresh now</button>
</div>
);
}
function fetchServerTime() {
return fetch("/api/time").then((response) => response.text());
}revalidate() targets whatever routes are currently active, so it pairs naturally with polling or a WebSocket handler like the one above. For a per-item alternative that can also submit its own data without navigating, see useFetcher(), covered elsewhere in this section.