Cheat SheetsReactRouting

Routing — Cheat Sheet

React · 5 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Routing
React5 topicsQuick revision reference
1

React Router — Routes, Links and Layouts

Routes map URLs to components, Link navigates without a reload, and nested routes let a layout stay mounted while only the inner part changes.

  • Nested routes keep a layout mounted while only the inner Outlet changes on navigation
  • Link and NavLink navigate client-side; a plain anchor to an internal route reloads the whole app
  • navigate(path, { replace: true }) avoids a history entry, which matters after a redirect
  • Always define a catch-all route so an unknown URL renders a real 404 page
  • Loaders fetch during the transition rather than after render, and errorElement contains failures to one subtree
Nested routes and Outlet
const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,          // header, nav, footer — stays mounted
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <Home /> },
      { path: 'problems', element: <ProblemList /> },
      { path: 'problems/:slug', element: <ProblemDetail /> },
      {
        path: 'account',
        element: <AccountLayout />,   // a nested layout with its own tabs
        children: [
          { index: true, element: <Profile /> },
          { path: 'billing', element: <Billing /> },
        ],
      },
      { path: '*', element: <NotFound /> },     // 404, always include one
    ],
  },
])

<RouterProvider router={router} />

// The layout renders <Outlet /> where the child route goes
function RootLayout() {
  return <><Header /><main><Outlet /></main><Footer /></>
}
2

Route Params and Search Params as State

Path params identify a resource, search params describe a view. Treating search params as your filter state makes every view shareable and refresh-proof for free.

  • Path params identify a resource; search params describe how the view is filtered or sorted
  • Params arrive as strings and are user input — parse, validate and handle the invalid case
  • Preserve other search params when setting one, and reset the page whenever a filter changes
  • Use replace for filter adjustments so the back button leaves the page rather than undoing ten tweaks
  • Never put tokens or personal data in a query string — it is logged, cached and sent in referrers
Params are strings, and they are user input
{ path: 'problems/:slug' }

const { slug } = useParams()      // always a string, never typed

// The URL is user input. Anyone can type /problems/../../etc
const { data, error } = useQuery({
  queryKey: ['problem', slug],
  queryFn: () => getProblem(slug),
})
if (error?.status === 404) return <NotFound />

// Numeric ids need parsing and checking
const id = Number(useParams().id)
if (!Number.isInteger(id)) return <NotFound />

// Optional and catch-all segments
{ path: 'problems/:slug?' }
{ path: 'docs/*' }                // useParams()['*'] holds the rest

// A changing param does NOT remount the component by default —
// the same route matched, so state persists across the navigation.
// Key on it when a fresh start is what you want:
<ProblemDetail key={slug} />
3

Protected Routes and Authorisation

A route guard is a UX affordance, not a security boundary. It hides what the user cannot use and sends them somewhere sensible — the server still decides what they may actually do.

  • A route guard is a UX affordance — the API must enforce the same rule, because the bundle is public
  • Check the auth "checking" state first, or the guard redirects signed-in users on every reload
  • Redirect with replace and carry the intended destination so sign-in returns the user where they were going
  • Signed out means redirect to sign in; signed in but not permitted means show a forbidden or upgrade screen
  • Prompt to reauthenticate in place rather than navigating away from a form with unsaved input
Redirect with the intended destination
function RequireAuth() {
  const { user, isChecking } = useAuth()
  const location = useLocation()

  if (isChecking) return <FullPageSpinner />       // not signed out — unknown

  if (!user) {
    return <Navigate to="/login" replace state={{ from: location }} />
  }
  return <Outlet />
}

// Wrap the protected subtree
{
  element: <RequireAuth />,
  children: [
    { path: 'dashboard', element: <Dashboard /> },
    { path: 'account', element: <Account /> },
  ],
}

// Return the user where they were going after sign-in
const from = location.state?.from?.pathname ?? '/dashboard'
navigate(from, { replace: true })

// replace on the redirect, or back returns them to the guard,
// which redirects again — a loop the user cannot escape.
4

Lazy Routes, Suspense and Loading Transitions

Splitting by route is the highest-value code splitting there is: a visitor to the home page should not download the admin panel or the code editor.

  • Route-level splitting is the highest-value split — visitors do not download features they never open
  • Place the Suspense boundary inside the layout so the shell does not flash on every transition
  • Prefetch a chunk on hover or focus so the navigation itself feels instant
  • useTransition keeps the current page interactive instead of dropping immediately to a fallback
  • After a deploy, old chunks disappear and lazy imports fail — catch that error and offer a reload
lazy + Suspense, boundary inside the layout
const Admin = lazy(() => import('./features/admin/AdminPage'))
const Editor = lazy(() => import('./features/editor/EditorPage'))

<Route
  path="/admin"
  element={
    <Suspense fallback={<PageSkeleton />}>
      <Admin />
    </Suspense>
  }
/>

// Put the Suspense boundary inside the layout, not around it —
// otherwise the header and nav disappear during every transition
// and the whole page flashes.

// Worth splitting: admin areas, the code editor, chart libraries,
// rich text editors, PDF generation, anything most visitors never open.
// Not worth splitting: small components on the critical path — an
// extra request costs more than the bytes saved.

// With React Router's own lazy, the route module itself is split,
// loader included:
{ path: 'admin', lazy: () => import('./routes/admin') }
5

Navigation Patterns — Scroll, Modals and 404s

The details that separate a single-page app from a website that feels right: where the page scrolls to, what the back button does to a modal, and what an unknown URL renders.

  • Scroll to top on a push navigation but let the browser restore position on back and forward
  • Move focus to the main heading after navigation, or screen-reader users are left behind
  • An overlay users might share or expect back to close belongs in the URL; an ephemeral one belongs in state
  • Distinguish a missing route from missing data — they need different pages and different words
  • A client-side route 404s on refresh unless the server returns index.html for unknown paths
Top on push, restore on pop, anchor on hash
// Without this, navigating from halfway down a list leaves the new
// page scrolled halfway down.
function ScrollToTop() {
  const { pathname, hash } = useLocation()
  const navigationType = useNavigationType()

  useEffect(() => {
    if (navigationType === 'POP') return          // back/forward: let the
                                                   // browser restore position
    if (hash) {
      document.querySelector(hash)?.scrollIntoView()
      return
    }
    window.scrollTo(0, 0)
  }, [pathname, hash, navigationType])

  return null
}

// React Router's <ScrollRestoration /> does this, including saving
// and restoring positions per history entry.

// Focus matters as much as scroll: after navigation, move focus to
// the main heading, or a screen-reader user stays where they were.
useEffect(() => { headingRef.current?.focus() }, [pathname])
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/react