TypeScript TS2339: Property href does not exist on type URL | Request

TypeScript TS2339: Property href does not exist on type URL | Request

Summary

In an Expo React Native app using tRPC, a developer attempted to convert values of type string | URL | Request into a URL string. TypeScript threw TS2339 when accessing .href because the union includes the Request type, which lacks this property. This report dissects the issue and presents a type-safe resolution.

Root Cause

  • TypeScript’s union type merges properties only if they exist on all members (string, URL, and Request).
  • Request objects (from Fetch API) lack .href. Only URL defines this property.
  • Type narrowing wasn’t sufficient: The initial typeof url === "string" check excluded strings but didn’t distinguish between URL and Request.

Why This Happens in Real Systems

  • Modern apps often accept multiple input types for flexibility (e.g., tRPC endpoints that handle raw strings or complex objects).
  • External libraries (like tRPC) or environments (React Native/Fetch API) introduce types unfamiliar to developers.
  • Developers assume URL-like structures share a common property (like .href), overlooking API inconsistencies.

Real-World Impact

  • Runtime Crashes: Undetected property access on Request could cause undefined exceptions.
  • Reduced Flexibility: Developers might avoid union types in critical logic to bypass the error, accepting less maintainable designs.
  • Debugging Overhead: Misleading error messages obscure how to securely handle different types.

Example or Code (if applicable)

Incorrect approach triggering TS2339:
typescript
const getUrlString = (url: string | URL | Request) => {
return typeof url === “string” ? url : url.href;
// Error: Property ‘href’ does not exist on ‘Request’
};

Corrected with exhaustive type guards:
```typescript
const getUrlString = (url: string | URL | Request) => {
  if (typeof url === "string") {
return url; // Strings are safe
  } else if (url instanceof URL) {
return url.href; // URL objects contain .href
  } else {
// Request objects must be handled differently
return url.url;
  }
};

## How Senior Engineers Fix It
1. **Direct Type Guarding**: Check `instanceof URL` to isolate `URL` objects safely.
2. **Leverage Built-in Methods**: Use `Request.prototype.url` explicitly for `Request` types.
3. **Avoid Unsafe Casts**: Reject `as any`/`as URL` to maintain type integrity.
4. **Union Handling Strategy**: Structure logic to exhaustively branch on every union member.
5. **Public API Contracts**: Verify input types via documentation (e.g., tRPC allows `string | URL | Request`).

## Why Juniors Miss It
- **API Familiarity Gap**: Lack of awareness that `Request` objects use `.url` instead of `.href`.
- **Overgeneralization**: Assuming `URL` and `Request` share identical interfaces.
- **Type Narrowing Oversight**: Failing to isolate `Request` beyond the `typeof "string"` check.
- **Workaround Bias**: Opting for `as any` to silence errors without analyzing root cause.