Let's look at your code with an additional strict feature enabled: noUncheckedIndexedAccess
This feature configures the compiler to warn you when you try to access a property on an indexed object by its indexed property accessor (in your case, an element in an array by a numeric index).
In any given array, there can't be an element at every possible numeric index: no array has infinite members. This setting recognizes that and helps you avoid potential related errors.
Here's your existing code in the TS Playground:
interface NumInterface {
key1: number | null;
}
const numList: NumInterface[] = [{ key1: 1 }];
const fooFunc = (index: number) => {
// #1
if (!numList[index].key1) return; /*
~~~~~~~~~~~~~~ Object is possibly 'undefined'.(2532) */
numList[index].key1 + 1; /*
~~~~~~~~~~~~~~ Object is possibly 'undefined'.(2532)
~~~~~~~~~~~~~~~~~~~ Object is possibly 'null'.(2531) */
// #2
const target = numList[index].key1; /*
~~~~~~~~~~~~~~ Object is possibly 'undefined'.(2532) */
if (!target) return;
target + 1;
};
Now, let's focus on what's happening inside your function body:
TS Playground
const numInterface = numList[index];
//^? const numInterface: NumInterface | undefined
const value = numInterface.key1; /*
~~~~~~~~~~~~
Object is possibly 'undefined'.(2532) */
If we access an element in the array by the index and assign it to a variable, the result will be the element at the index (if it exists in the array) or undefined (if it doesn't).
Then we can try to access the property key1 of the numInterface variable to assign it to a new variable value: If the numInterface didn't exist at the index we used, then — at runtime — an error will occur: TypeError: Cannot read properties of undefined. TypeScript is warning us about this and trying to prevent us from making a mistake.
In order to fix this, we need to be sure that the numInterface exists before trying to access its key1 property, and we can use an if statement to do so:
TS Playground
const numInterface = numList[index];
//^? const numInterface: NumInterface | undefined
if (numInterface) {
const value = numInterface.key1;
//^? const value: number | null
}
But if we try to add 1 to the value at this point, we'll see another compiler error because the value is still potentially null:
TS Playground
const numInterface = numList[index];
//^? const numInterface: NumInterface | undefined
if (numInterface) {
const value = numInterface.key1;
//^? const value: number | null
value + 1; /*
~~~~~
Object is possibly 'null'.(2531) */
}
To do this safely, we need another conditional check, to be sure that the value is a number type before performing the addition operation:
TS Playground
const numInterface = numList[index];
//^? const numInterface: NumInterface | undefined
if (numInterface) {
const value = numInterface.key1;
//^? const value: number | null
if (typeof value === 'number') {
value + 1; // Ok!
}
}
Now the compiler doesn't complain about anything else because we've performed all the checks needed to be sure that the values are of the expected types and that they're being used appropriately.
If you only ultimately need the value at the key1 property on the element, then you can use the Optional chaining operator (?.) to access the property in a single expression — and in the case that the element doesn't exist, the expression will short-circuit and evaluate to undefined (as you can see in the inferred union type below):
TS Playground
const value = numList[index]?.key1;
//^? const value: number | null | undefined
if (typeof value === 'number') value + 1; // Ok!
It takes a bit more syntax to write type-safe code sometimes, but it's worth it to know that you've prevented errors and it can help you to have greater confidence about the performance and correctness of your code.
[]is allowed to change the items in the array. After the firstif,numList[index]could be returning something else...if (!numList[index]) return;check at the beginning of your function so that we don't need to talk about unchecked indexed accesses, which isn't the point of your question?