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, andRequest). Requestobjects (from Fetch API) lack.href. OnlyURLdefines this property.- Type narrowing wasn’t sufficient: The initial
typeof url === "string"check excluded strings but didn’t distinguish betweenURLandRequest.
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
Requestcould 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.