Building a Multi-Page SPA with React RouterEntry 12 of 20
Pending UI with useNavigation
useNavigation() returns the router's current navigation state, so you can show feedback while a loader or action runs.
Notes
navigation.stateis'idle','loading'(a loader is running), or'submitting'(an action is running).- Since loaders run before the next page renders, the indicator belongs on a page that's already visible, usually the root layout.
navigation.formDataholds the data being submitted during'submitting'.
What / Why / When
- What
Global navigation/submission status.
- Why
Without it, the app seems frozen while slow loaders run.
- When
Any route with a loader or action that can take noticeable time.
Code compare
// pages/Root.tsximport { Outlet, useNavigation } from "react-router";export default function RootLayout() { const navigation = useNavigation(); return ( <> <MainNavigation /> <main> {navigation.state === "loading" && <p>Loading...</p>} <Outlet /> </main> </> );}@Component({ selector: "app-root-layout", imports: [RouterOutlet, MainNavigationComponent], template: ` <app-main-navigation /> <main> @if (isLoading()) { <p>Loading...</p> } <router-outlet /> </main> `,})export class RootLayoutComponent { private router = inject(Router); protected isLoading = toSignal( this.router.events.pipe( filter( (e) => e instanceof NavigationStart || e instanceof NavigationEnd || e instanceof NavigationCancel || e instanceof NavigationError, ), map((e) => e instanceof NavigationStart), ), { initialValue: false }, );}Differences and gotchas
- React Router gives one value for loading and submitting. Angular router events cover navigation only; form submission state lives in the component (Entry 17).
Quick reference
| React | Angular |
|---|---|
useNavigation().state === 'loading' | NavigationStart → NavigationEnd events |
state === 'submitting' | Component signal (isSubmitting) |