Donphin
Donphin

Reputation: 31

Typescript - Why are objects with optional values still optional in if check?

I saw this at work recently and I was wondering why Typescript doesn't understand the code below. If test is either undefined or a string, shouldn't it understand that it has to be a string if it's in an if statement?

interface IObj {
  test?: string;
}

const obj: IObj = {
  test: "why does this not work?",
};

if (obj.test) {
  const { test } = obj;

  () => testFunc(obj.test); // error (why does this not work?)
  () => testFunc(test); // works 
}

function testFunc(testString: string) {
  return testString;
}
  1. I create an object (obj) with an optional value "test".
  2. I check the optional value in an if.

Destructuring works, but not if you use the object and value right away.

Upvotes: 2

Views: 484

Answers (2)

Rohling
Rohling

Reputation: 297

An attribute with ? (like test in IObj) has the possibility of being undefined, so the type of test is string | undefined.

When you check for the existence of the test attribute in your if statement, you are explicitly saying that test must not be undefined to enter the block. Accessing obj.test or your newly created variable test should return a string.

However, the problem is that you are using obj.test inside an arrow function, which creates a new scope and loses the validation you did earlier. Basically, for typescript, there's no way to guarantee that obj.test won't be undefined when the function is called.

This problem does not occur with the test variable because when you create it inside the if statement, the obj.test is a string, so test is also a string, with no possibility of undefined.

Consider the following example:

if (obj.test) {
  const { test } = obj;

  setTimeout(() => {
    testFunc(obj.test);
    testFunc(test);
  }, 5000);

  obj.test = undefined;
}

setTimeout also creates another scope and, as you might know, it will execute the function after X milliseconds (5 seconds in this case). Since it's not blocking, the flow of execution will reach obj.test = undefined before testFunc(obj.test) within setTimeout. This is an example of a problem that can occur.

Upvotes: 4

TmTron
TmTron

Reputation: 19381

Since typescript 4.9 you can use the new satisfies operator instead of the type-assertion:

interface IObj {
  test?: string;
}

const obj = {
  test: "why does this not work?",
} satisfies IObj;

if (obj.test) {
  const { test } = obj;

  () => testFunc(obj.test); // works
  () => testFunc(test); // works 
}

function testFunc(testString: string) {
    console.log(testString);
  return testString;
}

Playground Example

Upvotes: 0

Related Questions