I found out a library named nprogress to show automatically a global progress bar into my react application. To do so I need to listen when it is navigating. I found the hook useNavigation from react-router-dom, which must be used within a RouterProvider but id does not support children. I tried to do this:
import { useEffect } from "react";
import "./App.css";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import HomePage from './HomePage.tsx',
import AboutPage from './AboutPage.tsx'
import {
Route,
Link,
useNavigation,
createBrowserRouter,
createRoutesFromElements,
RouterProvider,
} from "react-router-dom";
const ProgressBarHandler = () => {
const navigation = useNavigation();
useEffect(() => {
if (navigation.state === "loading") {
NProgress.start();
} else {
NProgress.done();
}
}, [navigation.state]);
return null;
};
const router = createBrowserRouter(
createRoutesFromElements(
<>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</>
)
);
function App() {
return (
<RouterProvider
router={router}
future={{
v7_startTransition: true,
}}
fallbackElement={<ProgressBarHandler />}
/>
);
}
The thing that I need is to make it start when it is navigating, because sometimes it takes few seconds to finish the render a complex page, and make it stop when it is done. I found out that useNavigation
is really cool because it manages automatically also the form actions and many other things.
Do you have any suggestion on how to use this hook or other hooks that keep the navigation monitored?
The router's fallbackElement
only renders when the apps routes are initially loading, after that it's no longer used.
The useNavigation
hook can only be used in a component within the router. I suggest creating a root layout route component that renders your ProgressBarHandler
(or directly applies the useEffect
to call NProgress
) and an Outlet
for nested routes.
Examples:
import { Outlet, useNavigation } from 'react-router-dom';
const ProgressBarHandler = () => {
const navigation = useNavigation();
useEffect(() => {
if (navigation.state === "loading") {
NProgress.start();
} else {
NProgress.done();
}
}, [navigation.state]);
return null;
};
const Layout = () => (
<>
<ProgressBarHandler />
<Outlet />
</>
);
import { Outlet, useNavigation } from 'react-router-dom';
const Layout = () => {
const navigation = useNavigation();
useEffect(() => {
if (navigation.state === "loading") {
NProgress.start();
} else {
NProgress.done();
}
}, [navigation.state]);
return <Outlet />;
};
This alone isn't quite enough to get a loading indicator working.
The trick to getting a navigation action loading indicator is to:
Add a route loader such that the navigation.state
can actually update to the "loading"
state value. This is because the loading state is only when
The loaders for the next routes are being called to render the next page
The emphasis is mine.
Re-validate each route change. By default route loaders are only called, or re-validated under specific conditions:
There are several instances where data is revalidated, keeping your UI in sync with your data automatically:
- After an action is called via:
<Form>
,<fetcher.Form>
,useSubmit
, orfetcher.submit
- When the
future.v7_skipActionErrorRevalidation
flag is enabled,loaders
will not revalidate by default if theaction
returns or throws a 4xx/5xxResponse
- You can opt-into revalidation for these scenarios via
shouldRevalidate
and theactionStatus
parameter- When an explicit revalidation is triggered via
useRevalidator
- When the URL params change for an already rendered route
- When the URL Search params change
- When navigating to the same URL as the current URL
See shouldRevalidate for details. Again, the emphasis is mine.
Put this all together and you have something like the following:
const router = createBrowserRouter(
createRoutesFromElements(
<Route
element={<Layout />}
shouldRevalidate={() => true} // <-- revalidate each route change
loader={() => null} // <-- loader to "revalidate"
>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Route>
)
);
function App() {
return <RouterProvider router={router} />;
}
Note: I removed the `v7_startTransition` future flag as this appears to interfere with route transitions and the `NProgress` UI you want to use. This flag is meant for:
This uses
React.useTransition
instead ofReact.useState
for Router state updates. View the CHANGELOG for more information.