왕뚜껑
왕뚜껑

Reputation: 73

TypeScript error: Type 'string' is not assignable to type 'number | ""'

I'm new to TypeScript and I'm having a hard time understanding the following error.

Type '{ order: string; }[]' is not assignable to type 'TestType[]'.
  Type '{ order: string; }' is not assignable to type 'TestType'.
    Types of property 'order' are incompatible.
      Type 'string' is not assignable to type 'number | ""'.

This is my test code

export enum ACTION_TYPES {
    TEST_ACTION = 'TEST_ACTION',
}


export type TestType = {
    order: number | '';
};

export function TestAction(
    testTypes: TestType[]
): {
    type: ACTION_TYPES.TEST_ACTION;
    testTypes: TestType[];
} {
    return {
        type: ACTION_TYPES.TEST_ACTION,
        testTypes,
    };
}

export type PluginsState = {
    testTypes: TestType[];
};

export type Actions =
    | ReturnType< typeof TestAction >;



const reducer = (
    state: PluginsState = {
        testTypes: [],
    },
    payload?: Actions
): PluginsState => {
    if ( payload && 'type' in payload ) {
        switch ( payload.type ) {
            case ACTION_TYPES.TEST_ACTION:
                return {
                    ...state,
                    testTypes: payload.testTypes,
                };
        }
    }
    return state;
};

export const stub = [
    {
        order: '',
    }
];


const defaultState: PluginsState = {
    testTypes: [],
};


reducer( defaultState, {
    type: ACTION_TYPES.TEST_ACTION,
    testTypes: stub,
} );

and this is TypeScript playground link

The error comes from the last line testTypes: stub

I'm pretty sure there is something I'm doing wrong, but I'm not understanding why '' cannot be used with number | ""

Is there a workaround to this error?

Upvotes: 1

Views: 2257

Answers (2)

Nishant
Nishant

Reputation: 55876

What you have written is correct from an author of the code perspective, this should be working fine. But, here is how Typescript sees it (rather, inferences it):

export const stub = [
    {
        order: '',
    }
];

This is essentially, of type: {order:string}[], which makes sense in most of the cases. However, this is a generalized version of your {order:''}[] so, Typescript cannot guarantee that whatever is passed is going to be {order: number|''}, you may mutate it and Typescript will still have to allow it. So, it warns you.

To avoid this inferencing you can do two things:

  1. Explicitly assign the type to stub as TestType so that TypeScript can error out when it is no more of that type, like this:

    export const stub: TestType[] = [
      {
        order: "",
      },
    ] ;
    
  2. Or, perform a const assertion on the mutable array elements promising that element is unchanging like this:

    export const stub = [
      {
        order: "",
      } as const,
    ] ;
    

Here is your working example: https://tsplay.dev/WvpdYN

Upvotes: 1

jcalz
jcalz

Reputation: 329923

When you write

export const stub = [
    {
        order: ''
    }
];

the compiler has to infer the type of stub. There are all kinds of types that it could be (such as unknown or any or Array<object>, etc). So the compiler uses heuristic rules to pick what it thinks is most appropriate. In the above case, the type inferred is:

/* const stub: Array<{order: string}> */

That's an unordered array of objects which must have an order property of type string. That's string, and not the string literal type "". Inferring string instead of "" is often exactly what people want in declarations like this: string-valued properties are often changed later:

stub[0].order = "somethingElse"; // okay

But unfortunately, that's not the type you wanted. When you later try to use it in as part of an argument you pass to reducer(), the compiler sees {order: string} as not assignable to TestType, and you get an error.


In order to fix this you need to either explicitly annotate stub's type to what you want:

export const stub: Array<TestType> = [ // annotated
    { order: '' }
]; 

reducer(defaultState, {
    type: ACTION_TYPES.TEST_ACTION,
    testTypes: stub, // okay
});

or you need to change how the value is inferred by the compiler, possibly by using a const assertion to keep "" as a string literal:

export const stub = [
    {
        order: '' as const, // const assertion
    }
];
/* const stub: Array<{ order: ""; } */

reducer(defaultState, {
    type: ACTION_TYPES.TEST_ACTION,
    testTypes: stub,
});

Playground link to code

Upvotes: 1

Related Questions