![]()
Handling Dynamic Routes Correctly in React Router
A common React Router mistake is improperly handling dynamic route parameters (like /user/:id), leading to mismatched routes or failed parameter extraction. Here’s how to do it right.
The Problem
// ❌ Common mistakes with dynamic routes
<Route path="/user/:id" element={<User />} />
// In User component:
function User() {
// Wrong ways to access params:
const { id } = useParams(); // ❌ Might be undefined if route doesn't match
const id = props.match.params.id; // ❌ v5 syntax in v6
}
Correct Solutions
1. Basic Dynamic Route Setup
// ✅ Route definition
<Routes>
<Route path="/user/:id" element={<User />} />
</Routes>
// User component
function User() {
const { id } = useParams(); // ✅ Correct in v6
return <div>User ID: {id}</div>;
}
2. Type Safety (TypeScript)
// Add type for params
type UserParams = {
id: string;
};
function User() {
const { id } = useParams<UserParams>(); // ✅ Typed params
return <div>User ID: {id}</div>;
}
3. Multiple Parameters
<Route path="/blog/:year/:month/:slug" element={<BlogPost />} />
function BlogPost() {
const { year, month, slug } = useParams();
// ...
}
4. Optional Parameters
<Route path="/search/:query?/:page?" element={<SearchResults />} />
function SearchResults() {
const { query = '', page = '1' } = useParams(); // Default values
// ...
}
Common Mistakes to Avoid
- Incorrect path syntax:
<Route path="/user/id" element={<User />} /> // ❌ Literal "id" not parameter
- Missing
useParamshook:
function User(props) {
const id = props.params.id; // ❌ Doesn't work in v6
}
- Not handling undefined:
const { id } = useParams();
useEffect(() => {
fetchUser(id); // ❌ id could be undefined if route mismatch
}, [id]);
- Incorrect nested routes:
<Route path="users" element={<UsersLayout />}>
<Route path=":id" element={<UserProfile />} /> // ✅ /users/123
<Route path=":id/edit" element={<EditUser />} /> // ✅ /users/123/edit
</Route>
Best Practices
- Validate parameters:
function User() {
const { id } = useParams();
if (!id) {
return <div>Invalid user ID</div>;
}
return <UserProfile id={id} />;
}
- Use TypeScript for type safety:
type Params = {
id: string;
tab?: 'posts' | 'photos';
};
const { id, tab = 'posts' } = useParams<Params>();
- Handle loading states:
function User() {
const { id } = useParams();
const [user, setUser] = useState(null);
useEffect(() => {
if (id) {
fetchUser(id).then(setUser);
}
}, [id]);
if (!user) return <Loader />;
return <UserProfile user={user} />;
}
- Normalize parameters:
const { id } = useParams();
const userId = parseInt(id || '', 10); // Convert to number
Advanced Patterns
1. Route Validation
<Route
path="/user/:id"
element={<User />}
loader={({ params }) => {
if (!isValidId(params.id)) {
throw new Response('Not Found', { status: 404 });
}
return fetchUser(params.id);
}}
/>
2. Custom Parameter Hooks
function useUserId() {
const { id } = useParams();
const navigate = useNavigate();
if (!id) {
navigate('/error');
return null;
}
return id;
}
3. Regular Expression in Paths
// Only match numeric IDs
<Route path="/user/:id(\\d+)" element={<User />} />
// Match specific formats
<Route path="/date/:year(\\d{4})/:month(\\d{2})" element={<DatePage />} />
Remember: Dynamic routes are powerful but require proper handling. Always:
- Define routes with clear parameter syntax (
:param) - Use
useParams()to access parameters - Validate parameters before use
- Handle loading and error states
- Consider TypeScript for type safety
For complex routing needs, explore React Router’s advanced features like loaders, regular expression paths, and route validation.
