Alex Kor
Alex Kor

Reputation: 85

How to destructure an array with the right Typescript typification?

I faced some difficulties with typescript destructuring array typification. Can't make array destructuring with typescript.

I got some data from a server. This data has the type (ISpecType[] | undefined)

interface ISpecType {
    id: string
    doughTypes: object[]
    sizes: object[]
    chosenDoughType: string
    chosenSize: number
    pizzaId: string
}

// omitted the fetch logic of getting data

// 'data' equals =
data = [
 {
  chosenDoughType: "Thin"
  chosenSize: 26
  doughTypes: (2) [{…}, {…}]
  id: "7p5Xca6DykcUkvE8A7P4p"
  pizzaId: "VYyLBL3l5JXhUAqXOb-rt"
  sizes: (3) [{…}, {…}, {…}]
 }
]

// the code where the error comes up.
 const [single]: ISpecType[] | undefined = data

After that, I got this message:

ERROR in src/features/pizzas/PizzaSpecsButtons.tsx:30:11
TS2461: Type 'ISpecType[] | undefined' is not an array type.
    28 |         }),
    29 |     })
  > 30 |     const [single]: ISpecType[] | undefined = data
       |           ^^^^^^^^

The type of data mustn't be changed!

How to tackle it?

Upvotes: 1

Views: 820

Answers (1)

jcalz
jcalz

Reputation: 330456

There are two possible failure cases you need to consider when you write const [single] = data, if all you know about data at compile time is that it is of type ISpecType[] | undefined.


First: data can be undefined. If so, then the line const [single] = data will attempt to destructure undefined, which will result in a runtime TypeError. That's why the compiler is giving you an error.

In order to prevent this from happening, you can use the nullish coalescing operator (??) to replace undefined with an empty array, so that single will be undefined:

const [single] = data ?? [];

But wait, when you check the type of single, the compiler thinks it is definitely ISpecType and not ISpecType | undefined:

// const single: ISpecType

Oops. That brings us to the second failure case.


Second: data can be an empty array. If so, then const [single] = data will end up making single undefined, but the compiler unfortunately does not catch this. TypeScript give arrays a numeric index signature, and traditionally assumes that if you index into an Array<X> with a numeric index, you'll get a value of type X. Yes, it's possible for it to be undefined, but the compiler doesn't consider the possibility. If you forget to check for undefined, you're likely to hit a runtime error even though the compiler thinks it's fine. This has been a longstanding issue; see microsoft/TypeScript#13778.

And you can actually enable the --noUncheckedIndexedAccess compiler option to enable stricter checking, at which point things would work:

// with --noUncheckedIndexedAccess enabled
const [single] = data ?? [];
// const single: ISpecType | undefined

But this compiler option is not part of the standard --strict suite of compiler options because it tends to be annoying to use, and it affects your whole code base. Instead of this I recommend providing an explicit undefined entry in your default array, so that the compiler is aware of the possibility:

// regardless of --noUncheckedIndexedAccess 
const [single] = data ?? [undefined]
// const single: ISpecType | undefined

Now you have a single of type ISpecType | undefined and the compiler is happy.

Playground link to code

Upvotes: 2

Related Questions