Using react-router 6.21.1 and the new data api, I have routes setup like:
const routes = [
{
element: <RootWrapper/>,
errorElement: <ErrorWrapper/>,
children: [
{path: "/login?", element: <LoginWrapper/>},
{path: "/logout", element: <Logout/>},
{
id: "AuthenticatedRoot",
element: <AuthenticatedRoot/>,
children: [
// ...
{path: "/dashboard", element: <Dashboard/>, loader: fetchDashboard},
]
}
]
]
My dashboard view is on display 24/7 and revalidates every 5 minutes. It needs to stay up through up to 5 errors to give at least 30 minutes for releases or other unknown downtimes. It was simple to do when doing manual useEffects for data fetching because I could just handle the error that returned from the fetch request:
await axios.get('dashboard')
.then(
response => {setData(response.data)},
error => {
setRetries(origRetries => {
if (origRetries >= 6) {
setError(true)
setErrorMessage(error.message)
return 0
} else {
return origRetries + 1
}
})
}
)
Is there anyway to do something like this with the new loaders?
You could write your fetchDashboard
loader function to close over the retry "state". An implementation may look similar to the following:
const fetchDashboard = () => {
let cache = {};
let retry = 0;
return async () => {
try {
const result = await axios.get('dashboard');
// Success, cache result and reset retry state
cache = result;
retry = 0;
return result;
} catch (error) {
// Failure/error, increment retry count
retry++;
if (retry > 5) {
// Rethrow error if ran out of retries
throw error;
} else {
// Otherwise return cached result
return cache;
}
}
};
};
const routes = [
{
element: <RootWrapper />,
errorElement: <ErrorWrapper />,
children: [
{ path: "/login?", element: <LoginWrapper /> },
{ path: "/logout", element: <Logout /> },
{
id: "AuthenticatedRoot",
element: <AuthenticatedRoot />,
children: [
// ...
{
path: "/dashboard",
element: <Dashboard />,
errorElement: <Dashboard />, // <-- render as error element too!
loader: fetchDashboard(), // <-- invoke outer function
},
]
}
],
},
];
import {
useRouteError,
useLoaderData,
useNavigate,
useNavigation
} from "react-router-dom";
const Dashboard = () => {
const navigate = useNavigate();
const { state } = useNavigation(); // "idle"/"loading", could be handy
const data = useLoaderData();
const error = useRouteError();
// Effect to trigger route revalidation, 5-minute interval
useEffect(() => {
const timer = setInterval(() => {
navigate(".", { replace: true });
}, 1000 * 60 * 5);
return () => clearInterval(timer);
}, []);
...
return (
...
);
};