Chapter 16 of 20 · React for Beginners — Complete Free Tutorial

Routing with React Router

Until now the React for Beginners series has built one-screen apps. Real applications have URLs: /, /about, /posts/42, each with its own view, its own back-button behaviour and its own shareable link. Because React renders a single page, URL handling is a library's job — and the standard is React Router. This chapter covers routes, links, parameters and the SPA fallback you will need when deploying in chapter 20.

How SPA routing works

The browser normally requests a new document per URL. A single-page app instead listens to navigation: when the URL changes, the router picks a component and React swaps the view — no document reload, no lost state. The router reads the location through the History API, the same browser facility behind history.pushState().

AspectMulti-page siteReact Router (SPA)
URL changeServer sends a new HTML documentRouter swaps components in place
On navigationFull reload, state lostFast, state preserved
Back buttonHandled by the browserRouter syncs via the History API
Direct URL openServer routes to a fileServer must serve index.html (fallback)

Install and wrap

React Router is a normal npm package. Recent versions export everything from react-router; the widely published react-router-dom package re-exports the same API:

npm install react-router-dom

Then wrap your app — usually in main.jsx — so every component can reach the router:

import { BrowserRouter } from 'react-router-dom';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>,
);

Expected output: no visible change yet — BrowserRouter provides routing context to everything inside it. Skipping this wrapper is the reason "useRoutes may be used only in the context of a Router" appears in the console.

Routes and Route

Declare your URL table declaratively. Routes scans its children and renders only the Route whose path matches the current URL:

import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}

Expected output: visiting /about renders the About component; an unknown URL falls through to * — the catch-all 404. Matching is exact by default, and the order does not matter; specificity decides.

Link and NavLink instead of anchor tags

A plain <a href> tells the browser to fetch a new document — which defeats the whole setup. Link intercepts the click and updates the URL through the router:

import { Link, NavLink } from 'react-router-dom';

<nav>
  <Link to="/">Home</Link>
  <NavLink to="/about" className={({ isActive }) => (isActive ? 'active' : '')}>
    About
  </NavLink>
</nav>

Expected output: clicking the nav updates the URL and view with no reload; the NavLink receives an isActive flag so the current page can be highlighted — a styling hook you will use in chapter 17.

Dynamic segments with useParams

Paths can contain parameters. A segment prefixed with : captures a value, and the matched component reads it with the useParams hook:

import { useParams, Link } from 'react-router-dom';

<Route path="/posts/:postId" element={<Post />} />

function Post() {
  const { postId } = useParams();
  const post = posts.find((p) => p.id === postId);

  if (!post) return <p>Post not found.</p>;
  return (
    <article>
      <h1>{post.title}</h1>
      <Link to="/posts">Back to posts</Link>
    </article>
  );
}

Expected output: /posts/42 renders post 42 with useParams() returning { postId: '42' }. Params are strings — parse and validate them before use, and handle the not-found branch as above.

Programmatic navigation

Sometimes navigation must happen in code — after a form submits, say. The useNavigate hook returns a function that pushes history entries:

import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();

  function handleSubmit(event) {
    event.preventDefault();
    // …authenticate…
    navigate('/dashboard');
  }
  // …fields…
}

Expected output: a successful submit lands on /dashboard with history behaviour identical to a link click. Pass { replace: true } to overwrite the current entry (so Back does not return to the form) and -1 to go back.

Nested routes and layouts

Routes nest: a parent route can render an outlet that its children fill in, which keeps shared chrome — navigation, sidebars — in one place:

<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<Overview />} />
  <Route path="settings" element={<Settings />} />
</Route>

// DashboardLayout renders <Outlet /> where child routes appear

Expected output: /dashboard shows Overview inside the layout, /dashboard/settings shows Settings in the same layout — the shell never remounts, so sidebar state survives navigation.

Common mistakes

  • Using <a href> for internal pages — every click performs a full reload; use Link or NavLink.
  • Forgetting the router wrapper — hooks like useNavigate throw outside BrowserRouter.
  • Missing the * route — unknown URLs render a blank screen instead of a 404.
  • Reading params as numbers — useParams returns strings; convert and guard before comparing.
  • Declaring hooks conditionally after navigation — call router hooks unconditionally at the top of the component, every render.

Practice checklist

  1. Install React Router and render three routes including a catch-all 404.
  2. Replace every internal anchor with Link and confirm no reloads occur (watch the Network tab).
  3. Build a /posts/:postId detail page that handles a missing id.
  4. Redirect after a fake login with useNavigate, using replace.
  5. Create one nested layout route with two children and verify the layout does not remount.

Your app now has pages. Next: make them look like pages — Styling React Applications.

Quiz: Routing with React Router

5 questions · 70% to pass

Frequently Asked Questions

React is a JavaScript library for building user interfaces from reusable components. It manages how your UI updates when data changes, so you describe what the screen should look like and React performs the efficient DOM updates. It is one of the most in-demand frontend skills and powers products from Netflix to Instagram, which makes it an excellent next step after core JavaScript.

You should be comfortable with functions, arrays and objects (map, filter, spread), destructuring, modules and basic async/await. React itself is written in plain JavaScript — JSX is a syntax layer on top. If any of those feel shaky, revisit the JavaScript fundamentals first and this series will go much faster.

React is a library focused only on the UI layer — you choose your own router, data layer and tooling. Angular is a full framework that ships routing, forms, HTTP client and testing conventions as one opinionated package. React's flexibility and huge ecosystem make it the common choice for teams that want to compose their own stack.

A component is a reusable function that returns UI. It receives input through props, keeps private data in state, and can render other components. Pages are composed by nesting small components (navbar, card list, footer) instead of one giant file, which keeps code testable and easy to change.

JSX is a syntax extension that looks like HTML but compiles to React's create() calls — it is a description of UI, not markup the browser parses. Small differences matter: class becomes className, style takes an object, and you embed JavaScript expressions inside curly braces. Once compiled, React turns that description into real DOM nodes.

Props are inputs passed from a parent — they are read-only inside the child, like function arguments. State is private, mutable data owned by a component; updating it triggers a re-render. The rule of thumb: values that come from outside the component are props; values that change over the component's lifetime are state.

Rendering means calling your component function to produce a description of the UI for the current state and props. It does not always mean DOM work — React compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM. This is why updating state 100 times does not mean 100 full page rebuilds.

An effect runs after the component has committed to the screen — after render, not during it. You list dependencies in an array, and React re-runs the effect only when one of them changes. If the effect returns a cleanup function, React runs it before the next re-run and on unmount, which is where you cancel timers, abort fetches and remove listeners.

Use Vite. Create React App is effectively in maintenance mode, while Vite starts instantly (native ES modules) and builds fast, and it is the default choice for new React projects. This series uses Vite for every example — you only need Node.js installed to follow along.

React apps compile to static files (HTML, CSS and JavaScript), so any static host works: Netlify, Vercel, GitHub Pages or Cloudflare Pages. Run the production build, upload the dist folder, and configure a single-page-application fallback so unknown paths load index.html — routing chapters explain why that fallback is required.