Skip to main content

Overview

React Router enables client-side routing in React applications, allowing you to build single-page applications with multiple views and seamless navigation without full page reloads.
This section covers React Router v6 features including nested routes, loaders, actions, and programmatic navigation.

Basic Router Setup

Set up React Router using createBrowserRouter and RouterProvider:
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import HomePage from './pages/Home';

const router = createBrowserRouter([
  { path: '/', element: <HomePage /> },
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;
The createBrowserRouter API is the recommended approach for React Router v6.4+ as it enables data loading and server-side rendering capabilities.

Nested Routes and Layouts

Create hierarchical route structures with shared layouts:
const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: 'events',
        element: <EventsRootLayout />,
        children: [
          { index: true, element: <EventsPage /> },
          { path: ':eventId', element: <EventDetailPage /> },
          { path: 'new', element: <NewEventPage /> },
          { path: ':eventId/edit', element: <EditEventPage /> },
        ],
      },
    ],
  },
]);

Key Concepts

  • Index routes - Default child route rendered at parent path
  • Dynamic segments - Use :parameter syntax for dynamic values
  • Nested layouts - Share UI elements across multiple routes
Use NavLink for navigation with active state styling:
import { NavLink } from 'react-router-dom';

function MainNavigation() {
  return (
    <nav>
      <ul>
        <li>
          <NavLink
            to="/"
            className={({ isActive }) =>
              isActive ? 'active' : undefined
            }
            end
          >
            Home
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/products"
            className={({ isActive }) =>
              isActive ? 'active' : undefined
            }
          >
            Products
          </NavLink>
        </li>
      </ul>
    </nav>
  );
}
The end prop ensures the NavLink is only active when the path matches exactly, useful for the home route.

Data Loading with Loaders

Load data before rendering route components:
const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    children: [
      {
        path: 'events',
        element: <EventsPage />,
        loader: async () => {
          const response = await fetch('http://localhost:8080/events');
          
          if (!response.ok) {
            throw new Error('Failed to fetch events');
          }
          
          const resData = await response.json();
          return resData.events;
        },
      },
    ],
  },
]);
Loaders execute before the route component renders, blocking navigation until data is fetched. Use defer for non-blocking data loading.

Form Submission with Actions

Handle form submissions using route actions:
import { redirect } from 'react-router-dom';

export async function action({ request, params }) {
  const data = await request.formData();
  
  const eventData = {
    title: data.get('title'),
    image: data.get('image'),
    date: data.get('date'),
    description: data.get('description'),
  };
  
  const response = await fetch('http://localhost:8080/events', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(eventData),
  });
  
  if (!response.ok) {
    throw new Response(JSON.stringify({ message: 'Could not save event.' }), {
      status: 500,
    });
  }
  
  return redirect('/events');
}

Handling Loading States

Provide user feedback during navigation:
import { useNavigation } from 'react-router-dom';

function AuthForm() {
  const navigation = useNavigation();
  const isSubmitting = navigation.state === 'submitting';
  
  return (
    <form method="post">
      {/* form fields */}
      <button disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Save'}
      </button>
    </form>
  );
}
useNavigation provides the current navigation state: idle, loading, or submitting.

Dynamic Routes and Parameters

Access URL parameters in your components:
import { useParams } from 'react-router-dom';

function EventDetailPage() {
  const params = useParams();
  const eventId = params.eventId;
  
  // Fetch and display event details
  return <div>Event ID: {eventId}</div>;
}

Query Parameters

Work with URL query parameters:
import { useSearchParams } from 'react-router-dom';

function AuthForm() {
  const [searchParams] = useSearchParams();
  const isLogin = searchParams.get('mode') === 'login';
  
  return (
    <form>
      <h1>{isLogin ? 'Log in' : 'Create a new user'}</h1>
      {/* form fields */}
    </form>
  );
}

Best Practices

Extract loader functions to separate files for better organization and reusability:
// loaders/events.js
export async function eventsLoader() {
  const response = await fetch('http://localhost:8080/events');
  
  if (!response.ok) {
    throw json({ message: 'Failed to fetch events' }, { status: 500 });
  }
  
  return response;
}
Use error boundaries and throw responses for proper error handling:
const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [/* routes */],
  },
]);
Use useNavigate for navigation in response to user actions:
import { useNavigate } from 'react-router-dom';

function MyComponent() {
  const navigate = useNavigate();
  
  function handleSubmit() {
    // Process data
    navigate('/success');
  }
  
  return <button onClick={handleSubmit}>Submit</button>;
}

Common Patterns

Protected Routes

Implement route protection with loaders:
export function checkAuthLoader() {
  const token = getAuthToken();
  if (!token) {
    return redirect('/auth');
  }
  return null;
}

Layout Wrappers

Create reusable layout components:
function RootLayout() {
  return (
    <>
      <MainNavigation />
      <Outlet />
    </>
  );
}

Authentication

Learn how to implement authentication with React Router

React Query

Alternative data fetching approach with caching

Build docs developers (and LLMs) love