Search code examples
typescriptconditional-statementsundefined

Typescript shows an error inside condition (IF) block where should be no undefined value


Got into a strange situation on a project. Here is an example where TS shows an error:

type Fruit = {
  name: string;
};

const myFruits: Fruit[] = [{ name: "Apple" }, { name: "Banana" }];

let asyncFuncResult: string | undefined = "NewName";
const asyncFuncResultConst: string | undefined = "NewName";
let result;

// Why TS shows an issue here?
if (asyncFuncResult) {
  result = myFruits.map(
    (fruit): Fruit => {
      return {
        ...fruit,
        name: asyncFuncResult
      };
    }
  );
}

It says for line name: asyncFuncResult that:

Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'

Screenshot of issue example

Here is the demo and ways I found to fix that: CodeSandBox Demo

But I don't get why it shows an error. We don't get into condition block if the variable is undefined. So there is no way we assign undefined value to a string.


Solution

  • Typescript is just pessimistic about control flow analysis (see #9998).

    The type of asyncFuncResult is string in the same scope where you checked the variable (inside the if).
    But when you use it in the callback of the map operator it is used in another scope - and since it is a variable (i.e. let) it could be changed by some other code before the callback is executed, so typescript pessimistically assumes string | undefined

    if (asyncFuncResult) {
      const aString = asyncFuncResult; // type of asyncFuncResult = 'string'
      result = myFruits.map(
        (fruit): Fruit => {
          return {
            ...fruit,
            name: asyncFuncResult // type of asyncFuncResult = 'string' | undefined
          };
        }
      );
    }
    

    I'd recommend using a constant instead:

    let asyncFuncResult: string | undefined = "NewName";
    const asyncFuncResultConst = asyncFuncResult;
    
    if (asyncFuncResultConst) {
      const aString = asyncFuncResultConst;
      result = myFruits.map(
        (fruit): Fruit => {
          return {
            ...fruit,
            name: asyncFuncResultConst
          };
        }
      );
    }
    

    The non-null assertion operator (!) would also work, but I try to avoid it as much as possible.