Question:
Why Typescript does not allow a union type in an array?

Problem

I'm having trouble understanding discriminated unions in typescript in the context of an array. Using the example from the >documentation, I would expect the below to be perfectly valid.


interface Circle {

  kind: "circle";

  radius: number;

}

 

interface Square {

  kind: "square";

  sideLength: number;

}

 

type Shape = Circle | Square;


const getShapes = (): Shape[] => {

    const data = [

        { kind: "circle", radius: 1 },

        { kind: "square", sideLength: 3}

    ]

    return data;

}


>playground link


My understanding is this is saying "a Shape must be either a Circle or a Square".


Instead it gives this error:


Type '({ kind: string; radius: number; sideLength?: undefined; } | { kind: string; sideLength: number; radius?: undefined; })[]' is not assignable to type 'Shape[]'. Type '{ kind: string; radius: number; sideLength?: undefined; } | { kind: string; sideLength: number; radius?: undefined; }' is not assignable to type 'Shape'. Type '{ kind: string; radius: number; sideLength?: undefined; }' is not assignable to type 'Shape'. Type '{ kind: string; radius: number; sideLength?: undefined; }' is not assignable to type 'Square'. Types of property 'kind' are incompatible. Type 'string' is not assignable to type '"square"'.


I'm specifically confused about seeing kind: string as how typescript interpreted that when it matches the literal. Based on how I'm reading the docs, this seems like a perfectly valid use for a union


Solution

When presented with a variable declaration const data = ⋯ with no >type annotation, TypeScript infers the type of the variable from the initializer, based on heuristic inference rules that work well in a wide range of scenarios. It does not currently have the ability to defer that and "look ahead" to see how the variable is used later. Given


const data = [

  { kind: "circle", radius: 1 },

  { kind: "square", sideLength: 3 }

];


the compiler infers


const data: ({

    kind: string;

    radius: number;

    sideLength?: undefined;

} | {

    kind: string;

    sideLength: number;

    radius?: undefined;

})[]


because that's what inference rules say to do. In particular, string literal properties get widened to string, since it's very common for people to modify the values of properties. The compiler does not see that you intend to return data as Shape[], so it doesn't know that you intend for the kind property to have string >literal types. By the time your return data is encountered, it is too late. The type of data is already set.


If you want to fix this you should either annotate data like const data: Shape[] = ⋯, or you could use >the satisfies operator on the initializer to give the compiler the context it is missing:


const getShapes = (): Shape[] => {

  const data = [

    { kind: "circle", radius: 1 },

    { kind: "square", sideLength: 3 }

  ] satisfies Shape[]

  return data;

}


>Playground link to code


Suggested blogs:

>How to Select checkboxes on an HTML treeview with JavaScript?

>How to use querySelectorAll()" with multiple conditions in JavaScript?

>How to fix mouseover event glitch in JavaScript?

>How to do light and dark mode in a website using HTML and JavaScript?

>How to manipulate manipulating Array object in JavaScript?

>How to merge an object into Array with the same key in JavaScript?

>Javascript Error Solved: Property 'id' does not exist on type 'T'

>Why highlighted table row using class not working in JavaScript?

>How to rename an object key based on the condition in JavaScript?

>How to sort an array based on another array in Javascript?

>Javascript: Modal not closing with a button


Nisha Patel

Nisha Patel

Submit
0 Answers