Learnwizy Technologies Logo

Learnwizy Technologies

Class 18: React Router

Traditional websites rely on server-side routing, where each URL change results in a full page reload. Modern web applications often use a Single Page Application (SPA) architecture, where the entire application loads once, and subsequent "page" navigations happen client-side without full reloads. React Router is the most popular library for handling client-side routing in React applications.


React Router: Client-Side Routing

In traditional multi-page applications, clicking a link causes the browser to request a new HTML page from the server, leading to a full page refresh. In contrast, Single-Page Applications (SPAs) load most of the necessary code (HTML, CSS, JavaScript) upfront, and then dynamically update the DOM with new data from the server, without the browser reloading entire new pages. This results in faster and more fluid user experience.

React Router is the standard library for client-side routing in React. It allows you to:

Core Components of React Router

Key components you'll typically use from react-router-dom:

Installation

First, you need to install react-router-dom in your React project:

npm install react-router-dom

The npm install command downloads the react-router-dom package and its dependencies from the npm registry and adds them to your project's node_modules directory, making them available for use in your React application.

Nested Routes

React Router also supports nested routes, allowing you to define child routes that render within their parent components. This is particularly useful for creating layouts where parts of the UI remain consistent while sub-sections change.

The <Outlet> component is used in parent route elements to render their child route elements. This means that when a parent route matches, its element is rendered, and then the child route's element is rendered inside wherever the <Outlet> is placed.

// src/components/Dashboard.jsx
import { Link, Outlet } from 'react-router-dom';

function Dashboard() {
  return (
    <div>
      <h2>User Dashboard</h2>
      <nav>
        <ul>
          <li>
            <Link to="/dashboard/profile">Profile</Link>
          </li>
          <li>
            <Link to="/dashboard/settings">Settings</Link>
          </li>
        </ul>
      </nav>
      {/* The Outlet renders the matched child route component */}
      <div>
        <Outlet />
      </div>
    </div>
  );
}

export default Dashboard;

// src/App.jsx
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Dashboard from "./components/Dashboard";

function Home() {
  return <h2>Welcome to the Home Page!</h2>;
}

function About() {
  return <h2>About Us</h2>;
}

function ProfileSettings() {
  return <h3>User Profile Settings</h3>;
}

function AccountSettings() {
  return <h3>Account Settings</h3>;
}

function App() {
  return (
    <Router>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/dashboard">Dashboard</Link>
          </li>
        </ul>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/dashboard" element={<Dashboard />}>
          <Route path="profile" element={<ProfileSettings />} />
          <Route path="settings" element={<AccountSettings />} />
        </Route>
      </Routes>
    </Router>
  );
}

export default App;

In this setup, when you navigate to /, the Home component will be rendered. When you navigate to /about, the About component will be rendered. The Link components ensure that navigation happens client-side without full page reloads.

When you navigate to /dashboard/profile, the Dashboard component will render, and inside its <Outlet>, the ProfileSettings component will be displayed.


Route Parameters

Often, you'll need routes that can accept dynamic values in the URL, such as an item ID, a user ID, or a product category. React Router allows you to define these dynamic segments as "route parameters."

You define a route parameter by prefixing a segment in your path with a colon (:), for example, /users/:id or /products/:category/:id.

Accessing Route Parameters with useParams Hook

The useParams hook from react-router-dom allows you to easily access these dynamic URL parameters within your component. It returns an object of key/value pairs of the dynamic params from the current URL that were matched by the <Route>.

// src/components/UserProfile.jsx
import { useParams } from 'react-router-dom';

function UserProfile() {
  const { id } = useParams(); // Access the 'id' parameter from the URL

  return (
    <div>
      <h2>User Profile</h2>
      <p>Displaying profile for user ID: {id}</p>
      {/* In a real app, you would fetch user data based on this ID */}
    </div>
  );
}

export default UserProfile;

// src/App.jsx (relevant part)
import UserProfile from "./components/UserProfile";

<Routes>
  {/* ... other routes ... */}
  <Route path="/users/:id" element={<UserProfile />} />
</Routes>

Now, if a user navigates to /users/123, the UserProfile component will render, and the id variable within it will be "123".


Programmatic Navigation

While <Link> components are excellent for user-initiated navigation, there are scenarios where you need to navigate programmatically based on certain actions or conditions, such as after a form submission, successful API call, or a login.

Using the useNavigate Hook

The useNavigate hook provides a function that lets you navigate programmatically. When you call this function, it pushes a new entry onto the history stack, similar to clicking a <Link>.

// src/components/LoginForm.jsx
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const navigate = useNavigate(); // Get the navigate function

  const handleSubmit = (e) => {
    e.preventDefault();
    // Simulate a login API call
    if (username === 'user' && password === 'password') {
      alert('Login successful!');
      navigate('/dashboard'); // Redirect to the dashboard page
      setUsername("");
      setPassword("");
    } else {
      alert('Invalid credentials');
    }
  };

  return (
    <div>
      <h2>Login</h2>
      <form onSubmit={handleSubmit}>
        <div>
          <label>Username:</label>
          <input
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
          />
        </div>
        <div>
          <label>Password:</label>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </div>
        <button type="submit">Login</button>
      </form>
    </div>
  );
}

export default LoginForm;

In this example, after a simulated successful login, navigate('/dashboard') is called to send the user to the /dashboard route. You can also pass a second argument to navigate for options like replace: true to replace the current entry in the history stack instead of pushing a new one, useful for preventing the user from navigating back to a login page after successful authentication.

Not Found (404) Route

In any web application, it's important to gracefully handle URLs that do not match any defined routes. This is commonly known as a "404 Not Found" page. React Router allows you to set up a catch-all route that renders a specific component when no other routes match the current URL.

Catch-all Route using path="*"

You can define a "catch-all" route by using path="*". This route should typically be placed as the last <Route> in your <Routes> component, as <Routes> renders the first matching route.

// src/components/NotFound.jsx
function NotFound() {
  return (
    <div>
      <h2>404 - Page Not Found</h2>
      <p>Sorry, the page you are looking for does not exist.</p>
    </div>
  );
}

export default NotFound;

// src/App.jsx (relevant part)
import NotFound from "./components/NotFound";

<Routes>
  {/* ... other routes ... */}
  <Route path="*" element={<NotFound />} /> {/* Catch-all route */}
</Routes>

With this setup, if a user tries to access a URL like /non-existent-page, the NotFound component will be rendered, providing a clear message that the page was not found.