Incorrect use of useParams() when destructuring

Loading

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

Featurev5v6
Hook NameuseParamsuseParams
Type InferenceLimitedImproved with generics
Optional ParamsAlways possibleExplicit with types
Path MatchingLooseExact

Best Practices

  1. Always Validate Parameters:
   const { orgId } = useParams();
   if (!orgId || !isValidId(orgId)) {
     return <Navigate to="/error" />;
   }
  1. Use TypeScript for Safety:
   interface Params {
     projectId: string;
     variant?: string;
   }

   const { projectId, variant = 'default' } = useParams<Params>();
  1. Handle Edge Cases:
  • Missing parameters
  • Malformed values
  • Unauthorized access attempts
  1. 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

  1. Assuming Parameter Existence:
   // ❌ Dangerous assumption
   const { mandatoryId } = useParams();
   useQuery(mandatoryId); // Could fail
  1. Ignoring Type Conversion:
   // ❌ String instead of number
   const { id } = useParams();
   fetch(`/api/items/${id + 1}`); // "51" instead of 6 if id was "5"
  1. 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:

  1. Validate parameters exist
  2. Handle type conversions explicitly
  3. Provide fallbacks for optional params
  4. Use TypeScript when possible
  5. Consider edge cases and error states

Leave a Reply

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