![]()
Proper Usage of useParams() in React Router v6
A common mistake when working with React Router’s useParams() hook is incorrectly destructuring or handling the route parameters, which can lead to runtime errors or unexpected behavior.
The Problem: Incorrect Destructuring Patterns
// ❌ Problematic implementations:
// 1. Destructuring non-existent params
const { userId, postId } = useParams(); // May be undefined
console.log(postId.toUpperCase()); // Runtime error if undefined
// 2. Assuming param types
const { id } = useParams();
const numericId = id + 10; // ❌ String concatenation instead of math
// 3. Not handling missing params
function UserPage() {
const { username } = useParams();
return <h1>{username}'s Profile</h1>; // Crash if undefined
}
Correct Implementation Patterns
1. Safe Destructuring with Fallbacks
function UserProfile() {
const { userId = 'default' } = useParams(); // Default value
return <div>Showing user: {userId}</div>;
}
2. Type Safety with TypeScript
type RouteParams = {
userId: string;
tab?: string; // Optional param
};
function UserPage() {
const { userId, tab = 'profile' } = useParams<RouteParams>();
return (
<div>
<h1>User ID: {userId}</h1>
<p>Viewing tab: {tab}</p>
</div>
);
}
3. Runtime Validation
function ProductPage() {
const { productId } = useParams();
if (!productId) {
return <NotFound />;
}
return <ProductDetails id={productId} />;
}
4. Numeric Parameter Conversion
function PostPage() {
const { id } = useParams();
const postId = parseInt(id || '', 10);
if (isNaN(postId)) {
return <InvalidPostId />;
}
return <Post postId={postId} />;
}
Key Differences from React Router v5
| Feature | v5 | v6 |
|---|---|---|
| Hook Name | useParams | useParams |
| Type Inference | Limited | Improved with generics |
| Optional Params | Always possible | Explicit with types |
| Path Matching | Loose | Exact |
Best Practices
- Always Validate Parameters:
const { orgId } = useParams();
if (!orgId || !isValidId(orgId)) {
return <Navigate to="/error" />;
}
- Use TypeScript for Safety:
interface Params {
projectId: string;
variant?: string;
}
const { projectId, variant = 'default' } = useParams<Params>();
- Handle Edge Cases:
- Missing parameters
- Malformed values
- Unauthorized access attempts
- Consider Helper Hooks:
function useValidatedParam<T>(name: string, validator: (val: string) => T) {
const params = useParams();
const value = params[name];
if (!value) throw new Error(`Missing ${name} param`);
try {
return validator(value);
} catch {
throw new Error(`Invalid ${name} param`);
}
}
// Usage:
const userId = useValidatedParam('userId', parseInt);
Common Pitfalls
- Assuming Parameter Existence:
// ❌ Dangerous assumption
const { mandatoryId } = useParams();
useQuery(mandatoryId); // Could fail
- Ignoring Type Conversion:
// ❌ String instead of number
const { id } = useParams();
fetch(`/api/items/${id + 1}`); // "51" instead of 6 if id was "5"
- Overlooking Optional Params:
// ❌ Might need default value
const { tab } = useParams();
const activeTab = tabs[tab]; // Could fail
Advanced Patterns
1. Parameter Transformation
function useNumericParam(name: string): number {
const params = useParams();
const value = params[name];
const number = Number(value);
if (isNaN(number)) {
throw new Error(`Invalid ${name} parameter: must be numeric`);
}
return number;
}
2. Multiple Route Parameters
type BlogParams = {
year: string;
month: string;
slug: string;
};
function BlogPost() {
const { year, month, slug } = useParams<BlogParams>();
if (!year || !month || !slug) {
return <NotFound />;
}
return <PostContent year={year} month={month} slug={slug} />;
}
3. With Query Parameters
function SearchPage() {
const { query } = useParams(); // From route /search/:query
const [searchParams] = useSearchParams(); // From URL ?sort=newest
const sortOrder = searchParams.get('sort') || 'relevance';
return <SearchResults query={query!} sort={sortOrder} />;
}
Remember: Proper handling of route parameters is crucial for application stability. Always:
- Validate parameters exist
- Handle type conversions explicitly
- Provide fallbacks for optional params
- Use TypeScript when possible
- Consider edge cases and error states
