A Step-by-Step Guide to Implementing React Router for Dynamic Single-Page Applications

Photo by RetroSupply on Unsplash

A Step-by-Step Guide to Implementing React Router for Dynamic Single-Page Applications

Single Page Applications (SPAs) are web apps that dynamically update a single page without requiring full page reloads. This results in a smooth user experience similar to a native mobile app.

React is a popular JavaScript library for building user interfaces and is commonly used for building SPAs. To handle navigation in a React SPA, we need a routing solution.

React Router is the most widely used routing library for React. It allows us to build a single page app with multiple views and navigational components while keeping the UI in sync with the URL.

In this comprehensive beginner's guide, we will cover the following:

  • What is client-side routing, and how does React Router implement it

  • Installing and setting up React Router in a React app

  • Creating routes to define app navigation

  • Matching route parameters to render dynamic components

  • Implementing nested routing to organize related screens

  • Building navigation with React Router links

  • Navigating programmatically with React Router hooks

  • Redirecting and preventing navigation

  • Animating transitions between routes

  • Lazy loading routes to optimize performance

  • Protecting routes with authentication

  • Handling 404 pages and route not found errors

  • React Router configuration and customization

Follow along as we explore each topic in detail and build a dynamic React app with routing from scratch!

What is Client-Side Routing?

Traditionally, routing was handled on the server-side. This required making a round-trip to the server anytime you needed to navigate to a new page.

With client-side routing, navigation happens on the front-end without requiring a server request. The router manipulates the browser history and updates the UI in response to URL changes.

Some benefits of client-side routing:

  • Faster page transitions without reloading

  • A smoother user experience similar to a mobile app

  • Easier to implement features like transitions and animations

React Router embraces client-side routing and gives us routing capabilities directly in our React app.

How React Router Works

React Router implements client-side routing by keeping your UI in sync with the browser URL.

It uses the browser history API to intercept URL changes and navigation events. When the URL changes, React Router will match a route, render the appropriate screen, and pass down router parameters to that screen.

The components of React Router include:

  • <BrowserRouter> - The main router component

  • <Route> - Defines our routes and renders components

  • <Link>/<NavLink> - Link components for navigation

  • useParams, useNavigate, etc - Router hooks

By wiring up <Route>'s and <Link>'s, we can build a complete client-side routing solution for our React Single Page App.

Set Up a React App

First, we need a React app to work with. Make sure you have Node.js installed, then run:

npx create-react-app my-app
cd my-app

This will scaffold a new React app using Create React App. Alternatively, you can use your preferred method to set up React.

When you create a new React app, it sets up a development server using webpack that bundles your app and automatically refreshes when you make changes.

The start script in your package.json runs this development server:

// package.json
"scripts": {
  "start": "react-scripts start" 
}

So after installing dependencies with npm install, you can run:

npm start

This will start the React dev server and open your app in the browser at http://localhost:3000.

Some key points on npm start:

  • It compiles your React code and bundles it ready for the browser

  • It watches for changes and automatically reloads the browser

  • It sets up Hot Module Replacement for instant refresh without losing state

  • It configures Webpack and Babel behind the scenes

  • The dev server supports hot reloading of CSS/modules without refreshing

npm start handles all the complex config required to run and develop a React app - it builds the app, runs the dev server, and refreshes as you code!

Once you're ready to deploy, you would then use npm run build to build the optimized production bundle output. But for development, npm start is all you need to start building your React app.

Installing React Router

To install React Router in your project:

npm install react-router-dom

This will install the react-router-dom package containing the routing components we need.

Alternatively, use Yarn:

yarn add react-router-dom

Now, let's set up a React Router in a React app.

Set Up React Router in the App

In your App.js, import the core routing components:

import { BrowserRouter, Routes, Route } from 'react-router-dom';

  • <BrowserRouter> - The router wrapper component

  • <Routes> - Holds all the <Route>'s

  • <Route> - Defines the routes

Wrap the app in <BrowserRouter>:

function App() {
  return (
    <BrowserRouter>
      <h1>React Router App</h1>
    </BrowserRouter>
  );
}

This sets up the basic React Router structure.

Defining Routes

The <Route> component defines our routes and renders UI when a location matches.

It takes two key props:

  • path - The route path URL

  • element - The JSX element to render

<Route path="/about" element={<About />} />

This will match the /about path and render the <About> component.

We can define multiple routes:

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="/contact" element={<Contact />} />
</Routes>

Now, different components will render on each route!

Exact Path Matching

By default, <Route> will match any path that starts with its path.

To make it match exactly, use the exact prop:

<Route exact path="/" element={<Home />} />

This will only render <Home> on the exact / URL.

Linking Between Routes

To link between routes, use the <Link> component:

<Link to="/about">About</Link>

This will render an anchor tag that navigates to /about when clicked.

We should use <Link> instead of anchor tags to enable client-side routing.

Matching Route Parameters

To match a dynamic segment in the URL, we can use the : colon syntax:

<Route path="/users/:id" element={<User />} />

This will match /users/1, /users/2, etc.

The :id parameter can be accessed inside <User> with the useParams hook:

let { id } = useParams();

We can now render user data dynamically based on the ID!

Nested Routing

We can render components as children of other routes to achieve nested routing:

<Route path="/users" element={<Users />}>
  <Route path=":id" element={<User />} />
</Route>

This will match nested paths like:

  • /users - Renders parent <Users>

  • /users/1 - Renders child <User>

Great for related screens!

Using Outlet for Nested Routes

The parent route element can render the child route using the <Outlet> component:

<Route path="/users" element={<Users />}>
  {/* Users.js */}
  <Outlet />
</Route>

This will render the child route element in the <Outlet> position.

Index Routes

We can define an "index" child route that renders when no child path is defined:

<Route path="/users" element={<Users />}>
  <Route 
    index
    element={<FeaturedUsers />} 
  />
  <Route 
    path=":id" 
    element={<User />} 
  />
</Route>

Now /users will render the <FeaturedUsers> index route.

Linking with Active Styling

The <NavLink> component adds styling when active:

<NavLink 
  to="/about"
  style={({ isActive }) => (isActive ? { color: 'red' } : undefined)}
>
  About
</NavLink>

This will turn red when on the /about page.

Navigating Programmatically

Sometimes, we want to navigate on some event like a form submission.

The useNavigate hook returns a navigate function to navigate programmatically:

import { useNavigate } from 'react-router-dom';
function Login() {
  let navigate = useNavigate(); 
  function handleLogin() {

    // Login logic...
    navigate('/dashboard');
  }
  return (
    <button onClick={handleLogin}>Login</button>
  );
}

Calling navigate('/dashboard') will navigate there.

The navigate function can also pass state:

navigate('/dashboard', { user: res.data.user })

And replace the current entry in history:

navigate('/dashboard', { replace: true })

Using Params and State

We can access route params, state, location etc. using hooks:

import { useParams, useLocation } from 'react-router-dom'
function Dashboard() {
  // Get route params
  let { userId }  = useParams();

  // Get route state
  let { state } = useLocation();
  return (
    <div>
      <h1>Dashboard</h1>
      <p>User ID: {userId}</p>
      <p>State: {JSON.stringify(state)}</p> 
    </div>
  );
}

This provides values from the matched route.

Redirecting

The <Navigate> component can redirect to another route:

<Route path="/login" element={<Login />} />
<Route 
  path="/dashboard"
  element={
    loggedIn ? <Dashboard /> : <Navigate to="/login" />
  }
/>

If not logged in, this will redirect to /login.

We can also use the replace prop to replace the history entry.

Handling 404 Pages

To show a 404 page on unknown routes, use a * catch-all route:

<Route path="*" element={<NotFound />} />

This will match any unknown route. We can also redirect to 404:

<Route path="*" element={<Navigate to="/" replace />} />

Now, unknown routes redirect to the home page.

Lazy Loading Routes

We can optimize our app by lazy loading route components only when needed:

const Users = lazy(() => import('./Users'));
function App() {
  return (
    <Routes>
      <Route 
        path="/users"
        element={
          <Suspense fallback={<Spinner />}>
            <Users />
          </Suspense>
        }
      />
    </Routes>
  );
}

Users will now load dynamically with React.lazy().

Animating Route Transitions

To animate transitions between routes, we can use:

  • CSS transitions based on location

  • React transition libraries like Framer Motion

  • Wrap routes in animated components

This enables smooth animated transitions as users navigate!

Scroll Restoration

By default, the scroll position resets on navigation.

To restore scroll on backward/forwards navigation:

<BrowserRouter>
  <ScrollRestoration />
</BrowserRouter>

<ScrollRestoration> will maintain scroll position.

Router Configuration

We can customize React Router with a <Router> component:

<Router
  basename="/app"
  history={createBrowserHistory()}
>
</Router>

Allows configuring:

  • basename - Base URL for all locations

  • history - Custom history implementation

And more!

Conclusion

In this guide, we've explored the core concepts and APIs that power routing in React with React Router, including:

  • Using <Route> and <Link> to define routes and navigate

  • Dynamic route matching with parameters and nested routes

  • Programmatic navigation with hooks like useNavigate

  • Redirecting, animating, lazy loading, and more

React Router provides a robust routing solution for React single-page apps right out of the box. With declarative routes, dynamic matching, and location tracking, you can build complex navigational experiences and keep your UI in sync with URLs.

There is still much more to cover with React Router, including protected routes, custom hooks, server-side rendering, and more.

I hope this guide provides a solid foundation for getting started with routing in your React apps!

If you want to become a programmer, then this article is your guide to becoming one. It explains everything from start to finish on how to build technical skills and what to do.

If you find this post exciting, find more exciting posts on Learnhub Blog; we write everything tech from Cloud computing to Frontend Dev, Cybersecurity, AI, and Blockchain.

Resource