1

Background

In Typescript, arrays don't have a size when initialised. Therefore, if you try to access an element of the array, it can be undefined:

const arrayImplicit = [item1, item2];
// ❌ possibly undefined
arrayImplicit[1].type; 

const arrayExplicit: MyItem[] = [item1, item2];
// ❌ possibly undefined
arrayExplicit[1].type;

These show up as errors if you turn on the option noUncheckedIndexedAccess.

Now, there are a couple of ways to overcome that. You can cast the array with as const when initialising, you can use a tuple, or you can use a non-null assertion operator when accessing:

const arrayWithCast = [item1, item2] as const;
// ✅ all good
arrayWithCast[1].type;

const tupleExplicit: [MyItem, MyItem] = [item1, item2];
// ✅ all good
tupleExplicit[1].type;

//  works, but annoying to work with and not a good practice
arrayImplicit[1]!.type; 
arrayExplicit[1]!.type;

Problem

However, tuples become cumbersome to work with when your array contains many elements. My use case is I am defining an array of mock data. This can contain dozens of elements, so I don't want to maintain a large tuple like this:

const mockUsers: [MockUser, MockUser, MockUser, MockUser, MockUser, MockUser, MockUser, MockUser, MockUser, MockUser, MockUser, MockUser] = ...

And casting an array as const only works for arrays with an implicitly inferred type. If you try to cast an array with an explicitly defined type (const array: MyItem[]), there is a type mismatch. If you use a readonly type (const array: readonly MyItem[]), the type mismatch is gone, but the elements are still possibly undefined:

// ❌ The type 'readonly [{ type: string; name: string; }, { type: string; name: string; }]' is 'readonly' 
// and cannot be assigned to the mutable type 'MyItem[]'.
const arrayExplicitWithCast: MyItem[] = [item1, item2] as const;

const arrayExplicitWithCastReadonly: readonly MyItem[] = [item1, item2] as const;
// ❌ possibly undefined
arrayExplicitWithCastReadonly[0].type; 

Finally, I don't want to use an implicit array like const array = [item1, item2, ...] as const;. If I do that, then typescript won't prevent me from inserting incorrect items into the array. Since these objects can contain many fields, and it is important that all the required fields be present, I need typescript to ensure that I only insert instances of MyItem:

const array = [
  item1,
  item2,
  // ❌ this should be an error:
  { some: "other", stuff: 12 },
] as const;

Live example

Check the full example in the typescript playground.


Question

How can I make it work without using tuples, and without having to use the non-null assertion operator (!), but still preserving the type-checks for each element in the array? The array is immutable, I only use it to populate the mock queries or assert values in tests.

szaman
  • 2,159
  • 1
  • 14
  • 30
  • no, defining an array like that still shows an error when trying to access an element – szaman Aug 03 '22 at 09:34
  • 2
    @szaman: did you check answers other than the accepted one? Because those define stricter types that seem to do what you want. – Joachim Sauer Aug 03 '22 at 09:42
  • 1
    This is an option: https://www.typescriptlang.org/play?noUncheckedIndexedAccess=true#code/C4TwDgpgBAsiCSwIFsoF4oG8qkgLigGdgAnASwDsBzAbigoENkIDjzqoBfGgKAGMA9hWJQySZAEZ0WHOBZQARADMBAhQBp6TeQoYATJgq69Bw4KPEAmadlw6ARgxIatzAgvtrjPHkoCuFHzAZEI4fmAANhAAPAAqAHwAFACUWDxQGVAkEMB+JBQZ0QCqUBAAHkgUeoRQANqxALoAPlCxtQ1JAHTdDARFqejxUAy8nD5R5gBuDBF+0Bi5kTFwiChJyYliKBKaW8iWybwTUI56AGozc9KLUdEr4uub4jsWKJaatnLuRpyHUAD0-1KJBIAhIQA – Titian Cernicova-Dragomir Aug 03 '22 at 09:44

0 Answers0