Skywrath
Skywrath

Reputation: 71

How to type a non-empty array in typescript?

I want the typescript compiler to throw an 'Object is possibly 'undefined'' error when trying to directly access any element of the array if the array is not pre-checked for emptiness, so that you always have to check that element for undefined, for example, using an optional chaining

If it is pre-checked that the array is not empty, then you need to be able to access its elements as usual, without the need to check its elements for undefined

I need this in order to be sure that the array is not empty, so if it is empty, then access to any of its elements will immediately return undefined then chaining will not continue and there will be no possible errors like cannot read property of undefined

How do i do this?

Code example, maybe it will make my question clearer

interface Element {
  a: {
    aa: string;
    bb: string;
  };
  b: {
    aa: string;
    bb: string;
  };
}

const element: Element = {
  a: { aa: "aa", bb: "bb" },
  b: { aa: "aa", bb: "bb" },
};

type ElementArray = Element[];

const array: ElementArray = [element, element];
const emptyArray: ElementArray = [];

const getFirstAWithoutLengthCheck = (array: ElementArray) => {
  return array[0].a; // i want the typescript compiler to throw an 'Object is possibly 'undefined'' error here
};

const getFirstAWithLengthCheck = (array: ElementArray) => {
  if (array.length) {
    return array[0].a; // shouldn't be any errors
  }
  return null;
};

const getFirstAOptChaining = (array: ElementArray) => {
  return array[0]?.a; // shouldn't be any errors
};

// will throw error cannot read property a of undefined, so we need to use
// optional chaining or length check in this function, but typesript is not requiring it
console.log(getFirstAWithoutLengthCheck(array)); // aa
console.log(getFirstAWithoutLengthCheck(emptyArray)); // crash!

// checking array length, access to first element should work as usual, no errors
console.log(getFirstAWithLengthCheck(array)); // aa
console.log(getFirstAWithLengthCheck(emptyArray)); // null

// optional chaining, no errors
console.log(getFirstAOptChaining(array)); // aa
console.log(getFirstAOptChaining(emptyArray)); // undefined

Upvotes: 3

Views: 2067

Answers (2)

Shivam Singla
Shivam Singla

Reputation: 2201

As commented by @Roberto Zvjerković, you need to use noUncheckedIndexedAccess compiler flag.

Playground (The link has noUncheckedIndexedAccess flag turned on).

However, you need to use instead of if (array.length), you need to do if (array[0]). It is because, even if the length is non-zero, it does not ensure that the elements are "non-undefined". If the array were array = [undefined], it should have given runtime error. It is irrespective of the type of array if it can contain undefined or not.

Upvotes: 3

blaumeise20
blaumeise20

Reputation: 2220

I managed to make TypeScript throw an error in the first example and not throw an error in the third one but it sadly gives you the undefined error in the second example. Hope this helps you:

interface Element {
  a: {
    aa: string;
    bb: string;
  };
  b: {
    aa: string;
    bb: string;
  };
}

const element: Element = {
  a: { aa: "aa", bb: "bb" },
  b: { aa: "aa", bb: "bb" },
};

type ElementArray = [] | [Element] | [Element, ...Element[]];

const array: ElementArray = [element, element];
const emptyArray: ElementArray = [];

const getFirstAWithoutLengthCheck = (array: ElementArray) => {
  return array[0].a; // i want the typescript compiler to throw an 'Object is possibly 'undefined'' error here
};

const getFirstAWithLengthCheck = (array: ElementArray) => {
  if (array.length) {
    return array[0].a; // shouldn't be any errors
  }
  return null;
};

const getFirstAOptChaining = (array: ElementArray) => {
  return array[0]?.a; // shouldn't be any errors
};

// will throw error cannot read property a of undefined, so we need to use
// optional chaining or length check in this function, but typesript is not requiring it
console.log(getFirstAWithoutLengthCheck(array)); // aa
console.log(getFirstAWithoutLengthCheck(emptyArray)); // crash!

// checking array length, access to first element should work as usual, no errors
console.log(getFirstAWithLengthCheck(array)); // aa
console.log(getFirstAWithLengthCheck(emptyArray)); // null

// optional chaining, no errors
console.log(getFirstAOptChaining(array)); // aa
console.log(getFirstAOptChaining(emptyArray)); // undefined

TypeScript playground Ignore the error on element, TypeScript playground thinks that element is a DOM element

Upvotes: 0

Related Questions