2

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.

1
  • 2
    Consider delete obj.test running at some point after the if but before () => testFunc(obj.test) executes. What would be the value passed into testFunc? What would be the value when you use () => testFunc(test) instead? Commented Dec 15, 2022 at 12:59

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

2 Comments

The setTimeout is slightly superfluous. Also, the fact it creates a new scope is prety much irrelevant. A simple way to demonstrate the problem is to just modify obj.test before calling the functions: tsplay.dev/w6LGym
@VLAZ I liked your example too, it's another way to demonstrate the problem. But i think that the fact it creates a new scope is very relevant for this question and it is very explicitly in your example too. The setTimeout is just a more 'didactic' example.
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.