Not handling dynamic routes correctly (/user/:id not being matched properly)

Loading

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

  1. Incorrect path syntax:
   <Route path="/user/id" element={<User />} /> // ❌ Literal "id" not parameter
  1. Missing useParams hook:
   function User(props) {
     const id = props.params.id; // ❌ Doesn't work in v6
   }
  1. Not handling undefined:
   const { id } = useParams();
   useEffect(() => {
     fetchUser(id); // ❌ id could be undefined if route mismatch
   }, [id]);
  1. 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

  1. Validate parameters:
   function User() {
     const { id } = useParams();

     if (!id) {
       return <div>Invalid user ID</div>;
     }

     return <UserProfile id={id} />;
   }
  1. Use TypeScript for type safety:
   type Params = {
     id: string;
     tab?: 'posts' | 'photos';
   };

   const { id, tab = 'posts' } = useParams<Params>();
  1. 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} />;
   }
  1. 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:

  1. Define routes with clear parameter syntax (:param)
  2. Use useParams() to access parameters
  3. Validate parameters before use
  4. Handle loading and error states
  5. Consider TypeScript for type safety

For complex routing needs, explore React Router’s advanced features like loaders, regular expression paths, and route validation.

Leave a Reply

Your email address will not be published. Required fields are marked *