Polyov
Polyov

Reputation: 2321

Typescript loses undefined context with forEach or map

I have a Typescript interface representing a user query. As such, it contains mostly optional fields:

interface InputData {
    fieldOne?: {
        requiredField: {
            key: string;
            values?: string[];
        }
    }
    fieldTwo?: {
        etc?: string;
    }
}

Using the --strictNullChecks flag with Typescript, I have to make sure that each property is not undefined before accessing it:

function do_something(input: InputData): void {
    const messages: string[] = [];
    if (input.fieldOne !== undefined) {
        if (input.fieldOne.requiredField.values !== undefined) {
            console.log(input.fieldOne.requiredField.values):
            // no errors
        }
    }
}

However, if I try and use an optional field inside of a forEach or map, even after checking if it is undefined, I recieve an error:

if (input.fieldOne !== undefined) {
    if (input.fieldOne.requiredField.values !== undefined) {
        input.fieldOne.requiredField.values.forEach(v => {
            messages.push(`${v} ${input.fieldOne.requiredField.key}`);
            // error!
        });
    }
}

"input.fieldOne" | Object is possibly undefined. (2532)

Here it seems like Typescript is "losing" the context that this field is not undefined inside of the forEach or map call.

How can I fix this error? Is there a better way to handle these undefined fields? Thanks!

(Here is a link to this error in the Typescript Playground.)

Upvotes: 3

Views: 818

Answers (1)

julianobrasil
julianobrasil

Reputation: 9377

Use the Non-Null assertion operator (!). You'll be telling typescript compiler: "Trust me, I know what I'm doing."

console.log(input.fieldOne!.requiredField.values):

Another approach would be creating another variable before the forEach statement, where typescript compiler still trust on if scope:

if (input.fieldOne) {
  const requiredField = input.fieldOne.requiredField;
  requiredField.values.forEach(v => messages.push(`${v} ${requiredField.key}`));
}

A third approach would be retesting input.fieldOne inside the forEach loop. It would cause a performance decrease though:

if (input.fieldOne) {
  input.fieldOne.requiredField.values.forEach(v => {
    if (input.fieldOne) {
      messages.push(`${v} ${input.fieldOne.requiredField.key}`);
    }
  });
}

Upvotes: 1

Related Questions