Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the object possibly undefined in typescript, when an explicit undefined check is added via function?

Below is a small example, where I would like to ask, why the eslinter for typescript is complaining about the object, that it could be possibly undefined in that case, where an undefined-check is actually added, just that it is extracted into a separate function, so that is gets a more meaningful name.

import { ReactElement } from "react";
import "./styles.css";

interface Item {
  id?: string;
  images?: string[];
}

const itemHasImages = (item: Item): boolean =>
  item.images !== undefined && item.images.length > 0;

const renderItem = (item: Item): ReactElement => {
  if(itemHasImages(item)){ // <<< using this the compiler complains below in JSX that item could possibly be null
    // vs if(item.images !== undefined && item.images.length > 0) <<< here the compiler recognizes the check
    return (
      <>
        <img src={item.images[0]} alt={""} />
      </>
    );
  } else {
    return <></>
  }
};

export default function App() {
  const dummyItems = [
    {
      images: ["https://picsum.photos/200/300", "https://picsum.photos/200/300"]
    },
    {
      images: ["https://picsum.photos/200/300", "https://picsum.photos/200/300"]
    }
  ];

  if (itemHasImages(dummyItems[0])) {
    console.log("hello")
    renderItem(dummyItems[0]);
  } else {
    return <div>Hello</div>;
  }
  return <div>Hello</div>;
}

Please apologize weak formatting, for better interaction you can find a code-sandbox link here:

https://codesandbox.io/s/unruffled-bogdan-c74u6?file=/src/App.tsx

like image 754
quizmaster987 Avatar asked Dec 13 '21 11:12

quizmaster987


People also ask

How do you prevent undefined in TypeScript?

To avoid undefined values when using or accessing the optional object properties, the basic idea is to check the property value using an if conditional statement or the optional chaining operator before accessing the object property in TypeScript.

Why is this undefined TypeScript?

Type 'undefined' is not assignable to type 'string'. This happens because TypeScript expects a specific value type but you're providing an incorrect value type.

How do you know if something is undefined in TypeScript?

We can use typeof or '==' or '===' to check if a variable is null or undefined in typescript.

What is undefined type in TypeScript?

Undefined is the default value for uninitialized variables Whenever we declare a variable without initializing it with a value, TypeScript initializes it as undefined .


1 Answers

In your posted code Typescript has no way of knowing that the boolean returned from your function is intended as a type guard for the item object.

To solve this you can declare a type predicate as the return type of your function indicating that Typescript should interpret the returned boolean as an assertion of the predicate. You will need to have a type/interface that describes an item with a required images property to use in the predicate.

You can either do this explicitly by extending the Item interface and making the images property required. (sandbox, ts-playground)

interface Item {
  id?: string;
  images?: string[];
}

interface ItemWithImages extends Item {
  images: string[];
}

function itemHasImages(item: Item): item is ItemWithImages {
  return item.images !== undefined && item.images.length > 0;
}

const renderItem = (item: Item): ReactElement => {
  if (itemHasImages(item)) {
    // vs if(item.images !== undefined && item.images.length > 0) <<< here the compiler recognizes the check
    return (
      <>
        <img src={item.images[0]} alt={""} />
      </>
    );
  } else {
    return <></>;
  }
};

Or you can implement the utility type described in the flagged duplicate which converts an optional property to a required one. (sandbox, ts-playground)

/**
 * @see https://stackoverflow.com/questions/52127082/ensure-existance-of-optional-property-in-typescript-interface
 */
 type MakeRequired<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> &
 { [P in K]-?: Exclude<T[P], undefined> };

interface Item {
 id?: string;
 images?: string[];
}

function itemHasImages(item: Item): item is MakeRequired<Item, "images"> {
 return item.images !== undefined && item.images.length > 0;
}

const renderItem = (item: Item): ReactElement => {
 if (itemHasImages(item)) {
   // vs if(item.images !== undefined && item.images.length > 0) <<< here the compiler recognizes the check
   return (
     <>
       <img src={item.images[0]} alt={""} />
     </>
   );
 } else {
   return <></>;
 }
};
like image 85
pilchard Avatar answered Sep 28 '22 13:09

pilchard