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

Below is a small example, where I would like to ask, why the eslinter for typescript is plaining about

Below is a small example, where I would like to ask, why the eslinter for typescript is plaining 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 piler plains below in JSX that item could possibly be null
    // vs if(item.images !== undefined && item.images.length > 0) <<< here the piler recognizes the check
    return (
      <>
        <img src={item.images[0]} alt={""} />
      </>
    );
  } else {
    return <></>
  }
};

export default function App() {
  const dummyItems = [
    {
      images: ["/200/300", "/200/300"]
    },
    {
      images: ["/200/300", "/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:

=/src/App.tsx

Below is a small example, where I would like to ask, why the eslinter for typescript is plaining 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 piler plains below in JSX that item could possibly be null
    // vs if(item.images !== undefined && item.images.length > 0) <<< here the piler 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

Share Improve this question asked Dec 13, 2021 at 11:01 quizmaster987quizmaster987 6791 gold badge8 silver badges16 bronze badges 3
  • 2 One option is to extend the Item interface with one requiring images . interface ItemWithImages extends Item {images: string[];} const itemHasImages = (item: Item): item is ItemWithImages => ... sandbox. Generic discussion here: Typescript user-defined type guard checking object has all properties in array – pilchard Commented Dec 13, 2021 at 11:38
  • 1 also see: Ensure existance of optional property in typescript interface – pilchard Commented Dec 13, 2021 at 11:46
  • Sandbox implementing the above, voting to close as duplicate. – pilchard Commented Dec 13, 2021 at 12:40
Add a ment  | 

3 Answers 3

Reset to default 3

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 piler 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./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 piler recognizes the check
   return (
     <>
       <img src={item.images[0]} alt={""} />
     </>
   );
 } else {
   return <></>;
 }
};

Why the eslinter for typescript is plaining about the object, that it could be possibly undefined?

The reason is because you added a ? (optional) to the property in the interface declaration.

One way to fix it is to check for undefined when rendering it with the ? operator.

<img src={item.images ? item.images[0] : ""} alt={""} />

Note that is only hiding the issue, the real solution is to make sure you don't render that element if there're no images, i.e. add a !!item.images?.length check.

Unfortunately TypeScript does not understand what your function itemHasImages exactly does, even if it has the exact same body as an expression that TS may understand when it is inlined.

Therefore when you factorize your check in an external function, TypeScript can no longer use it to perform type narrowing (here to make sure that an optional property is actually present).

This is one of the cases where TypeScript may force us not to be "too smart" and leave an inline check.

Another acceptable solution is to force / cast the type after your check:

  if(itemHasImages(item)){
    const images = item.images as unknown as string[]; // Force type since we have externally verified it
    return (
      <>
        <img src={images[0]} alt={""} />
      </>
    );
  }

A more plex solution might be possible, by providing overload definitions for your itemHasImages function, so that TS can use them to perform type narrowing.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745220497a4617226.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信