TS2339

Property 'X' does not exist on type 'Y'

๐Ÿ“š Difficulty: Beginner โฑ๏ธ 5 min read ๐Ÿ”„ Updated: Dec 2024

What is TS2339?

TS2339 occurs when you try to access a property that TypeScript doesn't recognize on a given type. This is one of TypeScript's most helpful errors as it catches typos, missing properties, and incorrect assumptions about object shapes at compile time.

// Example of TS2339 error
interface User {
    name: string;
    age: number;
}

const user: User = { name: "John", age: 30 };
console.log(user.email); // Error: Property 'email' does not exist on type 'User'

Common Causes

1. Typo in Property Name

The most common cause is simply misspelling a property name:

interface Config {
    apiEndpoint: string;
    timeout: number;
}

const config: Config = { apiEndpoint: "/api", timeout: 5000 };

// โŒ Wrong - typo
console.log(config.apiEndPoint); // Error! (capital P)

// โœ… Correct
console.log(config.apiEndpoint);

2. Missing Property in Interface

The property exists on the object but not in the type definition:

// โŒ Interface is incomplete
interface User {
    name: string;
}

const user: User = { name: "John" };
console.log(user.email); // Error!

// โœ… Add the missing property to interface
interface User {
    name: string;
    email?: string; // Optional property
}

3. Accessing Properties on Union Types

When a variable can be multiple types:

interface Dog { bark(): void; }
interface Cat { meow(): void; }

function makeSound(animal: Dog | Cat) {
    animal.bark(); // Error! Cat doesn't have bark()
}

// โœ… Use type narrowing
function makeSound(animal: Dog | Cat) {
    if ('bark' in animal) {
        animal.bark(); // OK - TypeScript knows it's a Dog
    } else {
        animal.meow(); // OK - TypeScript knows it's a Cat
    }
}

4. Object from External Source (API, JSON)

Data from external sources isn't type-checked:

// โŒ Unknown shape from API
const data = await fetch('/api/user').then(r => r.json());
console.log(data.name); // Error! 'data' is 'any' or unknown

// โœ… Define and assert the type
interface UserResponse {
    name: string;
    email: string;
}

const data: UserResponse = await fetch('/api/user').then(r => r.json());
console.log(data.name); // OK

Solutions

1. Add Property to Interface

interface User {
    name: string;
    email: string;  // Add the missing property
    phone?: string; // Use ? for optional properties
}

2. Use Type Narrowing

function process(input: string | string[]) {
    if (typeof input === 'string') {
        console.log(input.toUpperCase()); // OK
    } else {
        console.log(input.length); // OK - array
    }
}

3. Use Optional Chaining

interface User {
    name: string;
    address?: {
        city: string;
    };
}

const user: User = { name: "John" };
console.log(user.address?.city); // undefined (no error)

4. Extend Existing Types

// Extending an interface
interface BaseUser {
    name: string;
}

interface AdminUser extends BaseUser {
    adminLevel: number;
}

const admin: AdminUser = { name: "Admin", adminLevel: 1 };
console.log(admin.adminLevel); // OK

5. Index Signature (Dynamic Properties)

// When you need dynamic property names
interface Config {
    [key: string]: string | number;
}

const config: Config = {};
config.anyProperty = "value"; // OK
config.anotherOne = 123; // OK

๐Ÿ’ก Pro Tip

Always prefer specific interfaces over index signatures when possible. Index signatures sacrifice type safety for flexibility.

Real-World Examples

Working with DOM Elements

const element = document.getElementById('myInput');

// โŒ Error - element might be null, and it's HTMLElement, not HTMLInputElement
console.log(element.value); // Error!

// โœ… Type narrowing with assertion
const input = document.getElementById('myInput') as HTMLInputElement;
if (input) {
    console.log(input.value); // OK
}

Event Handling

// โŒ Generic Event doesn't have 'target.value'
function handleChange(e: Event) {
    console.log(e.target.value); // Error!
}

// โœ… Use specific event type
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    console.log(e.target.value); // OK
}

โš ๏ธ Avoid This

Don't use (obj as any).property to silence this error. It defeats the purpose of TypeScript and can lead to runtime errors.

Summary

Still Confused by Your Error?

Paste your TypeScript error into our analyzer for instant, detailed explanations.

Try Error Analyzer โ†’