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.state is '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.formData holds 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

React
// 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>    </>  );}
Angular
@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

ReactAngular
useNavigation().state === 'loading'NavigationStart → NavigationEnd events
state === 'submitting'Component signal (isSubmitting)