Module 7.2: Dynamic Routes & Data Mutations

🎯 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) usinguseSearchParams. - Data Mutations (Actions): (Advanced) Submitting data to the server (Form Submit) using the Data API.
- Pending UI: Handling the pending
UIduring 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)

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.

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.

3.1 Action Workflow
- The user clicks Submit within a
<Form>. - The Router intercepts the browser request and sends it to the
actionfunction. - The
actioncalls an API (POST/PUT/DELETE). - After the
actioncompletes, the Router automatically re-runs allloadersto 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.

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.
- Dynamic route
- Techniques:
loader,useParams,useSearchParams,throw Response(404).

💡 Hints:
- The
loaderreceivesparamsto extractalbumId.- If
albumIdis 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 needinguseState. - Optimistic UI: The
UIupdates instantly when the button is clicked.
- Sidebar: Load user lists (
- Techniques:
action,<Form>,useFetcher(for actions that don’t trigger page navigation).

💡 Hints:
- The
actionreceivesformData-> Updates Database -> Returns null.- The Router will automatically re-run the
Loader-> The Sidebar automatically updates.- Use
useFetcherfor the Star button to avoid unnecessary page navigation/sidebar reloads.
6. Summary
| Concept | Code Keyword | Purpose |
|---|---|---|
| Dynamic Params | :id / params | Dynamic routing, loading detailed data. |
| Query Params | useSearchParams | Filter, Sort, Pagination on the URL. |
| Data Write | action / <Form> | Handle form submissions, modify data. |
| UI Feedback | useNavigation | Display Loading bar, disable buttons. |
7. Homework
Try integrating the Search feature into your Lab 2 (Crypto Nexus) project:
- Add a search input bar.
- When typing, update the URL to
?q=bitcoin. - In the
loader, readrequest.urlto extract the query and filter the returned coin list.