Vitaly Korneev
Vitaly Korneev

Reputation: 33

Why Typescript throw error message: Property does not exist on type?

I created two code examples. The only difference between them is what expression I pass to the switch operator.

In a first case I use an object property. And it works fine.

In a second case I create a type variable. And Typescript throw an error message:

Property 'name' does not exist on type 'Action'.

Property 'name' does not exist on type '{ type: "reset"; }'.

Why is this happening?

The object property action.type and variable type are of the same type 'reset' | 'update' .

interface State {
    name: string;
    cars: any[];
}

type Action = { type: 'reset' } | { type: 'update', name: string };

function reducer(state: State, action: Action): State {
    switch (action.type) {
        case 'update':
            return { ...state, name: action.name };
        case 'reset':
            return {...state, cars: [] };
        default:
            throw new Error();
    }
}

interface State {
    name: string;
    cars: any[];
}

type Action = { type: 'reset' } | { type: 'update', name: string };

function reducer(state: State, action: Action): State {
    /**
    * Create a 'type' variable
    */
    const { type } = action;

    switch (type) {
        case 'update':
            return { ...state, name: action.name };
    /**
    * Typescript will throw an error message
    * Property 'name' does not exist on type 'Action'.
    * Property 'name' does not exist on type '{ type: "reset"; }'.
    */
        case 'reset':
            return {...state, cars: [] };
        default:
            throw new Error();
    }
}

Image description

Upvotes: 3

Views: 1077

Answers (2)

kaya3
kaya3

Reputation: 51162

Basically, Typescript doesn't keep track of a relationship between the types of the variables action and type; when type's type is narrowed (such as in a case of a switch statement), it doesn't also narrow action's type.

On the assignment const { type } = action;, the compiler infers type: Action['type'], which happens to be 'reset' | 'update'. Later, the case expression doesn't narrow the type of action because no type-guarding check was done on action.

For this to behave the way you want it to behave, the compiler would have to introduce a type variable T extends Action['type'] and infer type: T, while simultaneously narrowing action to the type : Action & { type: T }. Then when type's type is narrowed, T itself would have to be narrowed, so the effect would propagate to action's type which would involve T.

Introducing a new type variable like this on every variable assignment, and control-flow narrowing the upper bounds of type variables, would greatly complicate the type-checking algorithm. It would also greatly complicate the inferred types making them harder for users to understand; so it's reasonable that Typescript doesn't do this. Generally speaking, type-checkers don't prove every provable property of your code, and this is an example.

Upvotes: 4

Ali Habibzadeh
Ali Habibzadeh

Reputation: 11577

When you refer to the argument it can infer the type from the whole object but when you create a constant you limit the type to simply become "reset" | "update" and the other bits of Action object type info is lost.

Upvotes: 1

Related Questions