Skip to Content

Module 7.2: Dynamic Routes & Data Mutations

Module 7.2 Banner: Dynamic Routes & Data Mutations overview

🎯 Module Goals

By the end of this module, you’ll master:

  • Dynamic Segments: How to define and extract dynamic parameters from URLs (e.g., /product/:id).
  • Loader with Params: Techniques for fetching detailed data based on URL parameters.
  • Search Params: Managing URL state (filter, sort, pagination) using useSearchParams.
  • Data Mutations (Actions): (Advanced) Submitting data to the server (Form Submit) using the Data API.
  • Pending UI: Handling the pending UI during page navigation or form submissions.

1. Dynamic Routing

1.1 Dynamic Segment Concept

In practice, we can’t manually define every route like /product/1, /product/2. React Router allows us to use a colon : to create variables in the URL.

const router = createBrowserRouter([ { path: 'products/:productId', // :productId is a dynamic variable element: <ProductDetail />, loader: productDetailLoader, }, ]);

1.2 Extracting Parameters in Components (useParams)

Use the useParams hook to extract values once the component has rendered.

import { useParams } from 'react-router-dom'; function ProductDetail() { const { productId } = useParams(); // If the URL is /products/iphone-15, then productId = "iphone-15" return <h1>Product: {productId}</h1>; }

1.3 Extracting Parameters in Loaders (params)

Diagram showing data flow with a loader, where parameters are passed to the loader function before the component renders.

This is a key difference in v6.4: The loader runs before the component renders, so hooks cannot be used. Instead, we must use the params argument injected into the loader function.

// file: productLoader.ts export const productLoader = async ({ params }) => { // params.productId corresponds to the ":productId" path const res = await fetch(`/api/products/${params.productId}`); if (!res.ok) { throw new Error('Product not found'); // Activates the errorElement } return res.json(); };

2. Search Parameters (Query String)

Handle URLs like /products?sort=price_asc&category=laptop. This state resides in the URL, allowing users to share filtered results with others.

Diagram illustrating how search parameters modify a URL to filter and sort product lists.

2.1 useSearchParams Hook

Works just like useState but synchronized with the URL.

import { useSearchParams } from 'react-router-dom'; function ProductList() { const [searchParams, setSearchParams] = useSearchParams(); // 1. Get value const sort = searchParams.get('sort'); // "price_asc" // 2. Set value const handleSort = () => { setSearchParams({ sort: 'price_desc', category: 'laptop' }); // URL changes to: /products?sort=price_desc&category=laptop // Page DOES NOT reload, Component re-renders }; return (/* UI */); }

3. Data Mutations (Actions & Form)

If loader is used for Reading data, then action is used for Writing (Write/Update) data.

Diagram illustrating the data mutation flow with actions, showing a form submission, action processing, and data revalidation.

3.1 Action Workflow

  1. The user clicks Submit within a <Form>.
  2. The Router intercepts the browser request and sends it to the action function.
  3. The action calls an API (POST/PUT/DELETE).
  4. After the action completes, the Router automatically re-runs all loaders to update the latest data (Revalidation).

3.2 Example: Edit User Name

Step 1: Create the Action

export const updateNameAction = async ({ request }) => { const formData = await request.formData(); const newName = formData.get('userName'); // Send to server for update await updateProfile({ name: newName }); return null; // Job done };

Step 2: Attach to the Route

{ path: 'profile', element: <Profile />, loader: profileLoader, action: updateNameAction, // <--- Register the action }

Step 3: Use the <Form> Component

import { Form } from 'react-router-dom'; function Profile() { return ( <Form method="post" action="/profile"> <input name="userName" type="text" /> <button type="submit">Save Changes</button> </Form> ); }

4. Pending UI (Global Loading)

When navigating pages or submitting a form, the app needs to respond immediately instead of freezing.

Diagram showing a loading spinner displayed during page transitions or form submissions for a better user experience.

4.1 useNavigation

This hook indicates what the Router is currently doing.

import { useNavigation } from 'react-router-dom'; function RootLayout() { const navigation = useNavigation(); // 'idle' | 'loading' | 'submitting' const isLoading = navigation.state === 'loading'; return ( <div> {isLoading && <GlobalSpinner />} {/* Full page loading */} <Outlet /> </div> ); }

4.2 Busy Button (UX Pattern)

Disable the button while the form is submitting to prevent spam clicks.

function SubmitButton() { const navigation = useNavigation(); const isSubmitting = navigation.state === 'submitting'; return <button disabled={isSubmitting}>{isSubmitting ? 'Saving...' : 'Save'}</button>; }

5. Hands-on Labs

Lab 1: “Album Detail” (Dynamic Loading)

Continuing from Lab 2 (Dashboard), we’ll build a detail page for a music Album.

  • Scenario: A user clicks on an album from the homepage.
  • Features:
    • Dynamic route /album/:albumId.
    • Fetch songs based on albumId.
    • Filter songs by name using useSearchParams.
  • Techniques: loader, useParams, useSearchParams, throw Response (404).

Screenshot of an album detail page displaying album information and a list of songs, with a search input for filtering.

💡 Hints:

  • The loader receives params to extract albumId.
  • If albumId is invalid, throw new Response("Not Found", { status: 404 }).
  • The search input uses onChange={(e) => setSearchParams({ q: e.target.value })}.

Lab 2: “Contact Manager” (CRUD & Mutations) [Advanced]

Build a simple contact application to fully understand the loader + action lifecycle.

  • Scenario: A management page for a list of employees/friends.
  • Features:
    • Sidebar: Load user lists (Loader).
    • Main: Display user details (Nested Routes).
    • Star Feature: A “Favorite” button using <Form method="post"> to update data without needing useState.
    • Optimistic UI: The UI updates instantly when the button is clicked.
  • Techniques: action, <Form>, useFetcher (for actions that don’t trigger page navigation).

Screenshot of a contact manager application, showing a sidebar with a list of contacts and a main section displaying a contact's details with a star button.

💡 Hints:

  • The action receives formData -> Updates Database -> Returns null.
  • The Router will automatically re-run the Loader -> The Sidebar automatically updates.
  • Use useFetcher for the Star button to avoid unnecessary page navigation/sidebar reloads.

6. Summary

ConceptCode KeywordPurpose
Dynamic Params:id / paramsDynamic routing, loading detailed data.
Query ParamsuseSearchParamsFilter, Sort, Pagination on the URL.
Data Writeaction / <Form>Handle form submissions, modify data.
UI FeedbackuseNavigationDisplay Loading bar, disable buttons.

7. Homework

Try integrating the Search feature into your Lab 2 (Crypto Nexus) project:

  1. Add a search input bar.
  2. When typing, update the URL to ?q=bitcoin.
  3. In the loader, read request.url to extract the query and filter the returned coin list.

8. References

Last updated on