L6: Extracting URL Parameters
Use the useParams hook to read dynamic values from the URL
In the last lesson, we created a dynamic route /listing/:id. Now we need to access that ID so we can use it to fetch the correct listing data!
React Router provides the useParams hook for exactly this purpose - it extracts parameters from the URL and makes them available in your component.
What You'll Learn
- What the
useParamshook does - How to extract URL parameters in components
- Understanding parameter data types
- Using parameters to fetch data
- Handling multiple parameters
The useParams Hook
useParams is a React Router hook that returns an object containing all URL parameters:
import { useParams } from 'react-router-dom';
function ListingDetailsPage() {
const params = useParams();
console.log(params); // { id: "42" } for URL /listing/42
return <div>Listing ID: {params.id}</div>;
}How it works:
- React Router sees you're on
/listing/42 - Matches it to route
/listing/:id - Extracts the value
"42"and names itid useParams()returns{ id: "42" }
Step 1: Import and Use useParams
Let's update ListingDetailsPage to extract the ID:
Add useParams to ListingDetailsPage
Open ListingDetailsPage.jsx and import the hook:
import { useParams } from 'react-router-dom';
export default function ListingDetailsPage() {
const { id } = useParams();
return (
<div className="container mx-auto px-4 py-8">
{/* Page Header */}
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-900 mb-2">
Cozy Beach House
</h1>
<div className="flex items-center gap-4 text-sm text-gray-600">
<span>๐ Malibu, California</span>
<span>โญ 4.8</span>
<span>๐ฅ 124 reviews</span>
</div>
{/* Display the ID from URL */} {}
<div className="mt-4 p-3 bg-blue-50 border border-blue-200 rounded"> {}
<p className="text-sm text-blue-800"> {}
<strong>Listing ID from URL:</strong> {id} {}
</p> {}
</div> {}
</div>
{/* Rest of the component stays the same... */}
{/* Image Placeholder */}
<div className="bg-gray-200 rounded-lg h-96 mb-6 flex items-center justify-center">
<p className="text-gray-500 text-lg">Image Gallery Coming Soon</p>
</div>
{/* Main Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Content sections... */}
<div className="lg:col-span-2 space-y-6">
<div className="border-b border-gray-200 pb-6">
<h2 className="text-xl font-semibold mb-2">
Entire place hosted by John
</h2>
<div className="text-gray-600">
4 guests ยท 2 bedrooms ยท 3 beds ยท 2 bathrooms
</div>
</div>
<div className="border-b border-gray-200 pb-6">
<h2 className="text-xl font-semibold mb-3">About this place</h2>
<p className="text-gray-600 leading-relaxed">
Wake up to the sound of waves in this stunning beach house.
</p>
</div>
</div>
{/* Sidebar */}
<div className="lg:col-span-1">
<div className="border border-gray-200 rounded-lg p-6 shadow-md sticky top-4">
<div className="mb-4">
<span className="text-2xl font-bold">$250</span>
<span className="text-gray-600"> / night</span>
</div>
<button className="w-full bg-pink-600 hover:bg-pink-700 text-white font-semibold py-3 px-6 rounded-lg transition-colors">
Check Availability
</button>
</div>
</div>
</div>
</div>
);
}What changed:
- Imported useParams - From 'react-router-dom'
- Called useParams() - Returns object with all parameters
- Destructured id - Extract the id property using
{ id } - Displayed the ID - Show it in a blue box for testing
Test Different URLs
Now try visiting different URLs in your browser:
Try these:
http://localhost:5173/listing/1 โ Shows "Listing ID from URL: 1"
http://localhost:5173/listing/42 โ Shows "Listing ID from URL: 42"
http://localhost:5173/listing/999 โ Shows "Listing ID from URL: 999"What you should see:
The blue box displays the ID from the URL! Each URL shows a different number.
Understanding useParams
Let's break down how useParams works:
Return value: An object with all parameters
import { useParams } from 'react-router-dom';
function Component() {
const params = useParams();
// URL: /listing/42
// params = { id: "42" }
return <div>ID: {params.id}</div>;
}When to use:
- When you have many parameters
- When parameter names might conflict with existing variables
- For debugging (see all parameters at once)
Extract specific parameters (Recommended)
import { useParams } from 'react-router-dom';
function Component() {
const { id } = useParams();
// URL: /listing/42
// id = "42"
return <div>ID: {id}</div>;
}When to use:
- Most common pattern
- Cleaner code
- Direct access to parameter values
Multiple destructuring:
const { id, section, page } = useParams();Route with multiple parameters:
// Route definition
<Route
path="/listing/:id/review/:reviewId"
element={<ReviewPage />}
/>
// Component
function ReviewPage() {
const { id, reviewId } = useParams();
// URL: /listing/42/review/7
// id = "42"
// reviewId = "7"
return (
<div>
<p>Listing: {id}</p>
<p>Review: {reviewId}</p>
</div>
);
}All parameters available at once!
Important: Parameters Are Strings
Parameters Are Always Strings!
URL parameters are always strings, even if they look like numbers!
const { id } = useParams();
// URL: /listing/42
console.log(id); // "42" (string)
console.log(typeof id); // "string"
// This might cause issues:
if (id === 42) {
// โ Never true! Comparing string to number
}
// Convert to number if needed:
const numericId = Number(id);
console.log(numericId); // 42 (number)
console.log(typeof numericId); // "number"
// Now comparison works:
if (numericId === 42) {
// โ
This works!
}When to Convert Types
Using the ID for Data Fetching
Now that we have the ID, we can use it to fetch the correct listing! (We'll implement this fully in Lesson 7)
Preview of what's coming:
import { useParams } from 'react-router-dom';
import { useState, useEffect } from 'react';
function ListingDetailsPage() {
const { id } = useParams(); // Get ID from URL
const [listing, setListing] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Fetch listing based on ID
fetch(`/api/listings/${id}`)
.then(res => res.json())
.then(data => {
setListing(data);
setIsLoading(false);
});
}, [id]); // Re-fetch when ID changes!
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h1>{listing.title}</h1>
<p>{listing.description}</p>
</div>
);
}Key points:
- Use
idin the API URL - Include
idin useEffect dependencies - Fetch re-runs when ID changes (navigating to different listing)
Handling Missing or Invalid IDs
What if someone visits /listing/ without an ID, or uses an invalid ID like /listing/abc?
URL: /listing (no ID)
What happens:
- Route
/listing/:iddoesn't match (ID required) - User sees blank page or 404
Solution: Add a separate route for listings list:
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/listing" element={<AllListingsPage />} />
<Route path="/listing/:id" element={<ListingDetailsPage />} />
</Routes>Now /listing shows all listings, /listing/42 shows one listing.
URL: /listing/abc or /listing/999999
What happens:
- Route matches (
:idaccepts any string) useParams()returns{ id: "abc" }or{ id: "999999" }- API call likely fails
Solution: Handle in data fetching (Lesson 7):
useEffect(() => {
fetch(`/api/listings/${id}`)
.then(res => {
if (!res.ok) throw new Error('Listing not found');
return res.json();
})
.catch(error => {
setError('Listing not found');
setIsLoading(false);
});
}, [id]);Best practice: Check if listing exists after fetching
function ListingDetailsPage() {
const { id } = useParams();
const [listing, setListing] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch(`/api/listings/${id}`)
.then(res => {
if (!res.ok) {
throw new Error('Listing not found');
}
return res.json();
})
.then(setListing)
.catch(err => setError(err.message));
}, [id]);
if (error) {
return (
<div className="text-center py-12">
<h1 className="text-2xl font-bold text-red-600">
{error}
</h1>
<p className="text-gray-600 mt-2">
Listing #{id} doesn't exist
</p>
</div>
);
}
// ... rest of component
}Multiple Parameters Example
If you ever need multiple URL parameters:
// Route definition
<Route
path="/listing/:listingId/booking/:bookingId"
element={<BookingDetailsPage />}
/>
// Component
function BookingDetailsPage() {
const { listingId, bookingId } = useParams();
// URL: /listing/42/booking/123
// listingId = "42"
// bookingId = "123"
return (
<div>
<h1>Booking Details</h1>
<p>Listing ID: {listingId}</p>
<p>Booking ID: {bookingId}</p>
</div>
);
}For StaySense, we only need one parameter (:id)!
Debugging useParams
If useParams() isn't working, check these:
Verify Route Configuration
Make sure the route has a parameter:
// โ
Has parameter
<Route path="/listing/:id" element={<Page />} />
// โ No parameter
<Route path="/listing" element={<Page />} />Check Component is Rendered by Router
useParams only works in components rendered by React Router:
// โ
Works - rendered by Router
<Route path="/listing/:id" element={<ListingDetailsPage />} />
// โ Doesn't work - rendered outside Router
function App() {
return <ListingDetailsPage />; // No route context!
}Console.log for Debugging
Add logging to see what you're getting:
function ListingDetailsPage() {
const params = useParams();
console.log('All params:', params);
console.log('ID:', params.id);
console.log('Type:', typeof params.id);
// ...
}Common useParams Patterns
You Can Now Access URL Parameters!
The useParams hook gives you access to dynamic route values. You can now use the ID to fetch listing-specific data in the next lesson!
Quick Recap
What we accomplished:
- โ
Imported and used
useParamshook - โ
Extracted
idfrom URL parameters - โ Displayed the ID in the UI
- โ Tested with different URLs
- โ Understood parameter types (always strings)
Key concepts:
- useParams hook - Extracts route parameters from URL
- Destructuring -
const { id } = useParams() - String type - Parameters are always strings
- Dynamic values - Different for each URL
- Dependencies - Include in useEffect when fetching
What's Next?
In Lesson 7, we'll use the id to fetch the actual listing data from the API! We'll implement loading states, error handling, and display real data based on the URL parameter. This is where everything comes together! ๐