Nadiely Jade
Nadiely Jade

Reputation: 255

filter in typescript got error of '(string | null)[]' is not assignable

I have an array of object, one of the property has a null value, I used filter to remove it, but typescript give me warning null is not assignable? since filter will never return null why typescript give me such an error?

const data = [
  {
    id: "123"
  },
  {
    id: "456"
  },
  {
    id: null
  }
];

const d2: string[] = data.map((d) => d.id).filter((o) => o);

console.log(d2);

demo https://codesandbox.io/s/gallant-water-vio56?file=/src/index.ts:0-165

Upvotes: 8

Views: 5521

Answers (2)

Best Codes
Best Codes

Reputation: 18

Using filter won't narrow down types for you. map transforms the data into an array of string | null data, and TypeScript doesn't know that d2 won't contain null after filtering (TypeScript isn't exceptionally bright, I guess…).

There are a couple of solutions — one of them is complicated, and the other is complicated (though not as complicated) compared to your original code.

Use type guarding

We can explicitly use types in the filter function itself so that TypeScript knows the output will not contain null:

const data = [
  { id: "123" },
  { id: "456" },
  { id: null }
];

// Type guard to filter out null values
const d2: string[] = data.map((d) => d.id).filter((o): o is string => o !== null);

console.log(d2);

Link to code

Cast the result, after filtering

We can also just cast the result after the filter is run instead of assigning the type beforehand. Just add as string[] to the end of your line:

const data = [
  {
    id: "123"
  },
  {
    id: "456"
  },
  {
    id: null
  }
];

const d2 = data.map((d) => d.id).filter((o) => o) as string[];

console.log(d2);

Note: It would be a good practice to include !== null as well, like this:

const d2 = data.map((d) => d.id).filter((o) => o !== null) as string[];

But it isn't necessary, and I was going for simplicity in the second solution…

Link to code

Upvotes: 0

Nicholas Tower
Nicholas Tower

Reputation: 84992

UPDATE SEPTEMBER 2024:

As of typescript 5.5, typescript is now able to infer type predicates.

However, the code in the question would still not work, and the reason for this is that the function might return an empty string. This possibility of an empty string means there's still a possibility you made a mistake, and so typescript keeps the error to force you to take a look.

You can either add an explicit return type (See the original answer below), or you can change the code so the empty string is no longer filtered out:

const d2: string[] = data.map((d) => d.id).filter((o) => o !== null);

Playground link


ORIGINAL ANSWER:

Typescript can't figure out that .filter((o) => o) will narrow the types of the array, not without some help. You can define a custom type guard and use that though. For example:

function notNull<T>(val: T | null): val is T {
    return val !== null;
}

const d2 = data.map((d) => d.id).filter(notNull); // d2 is a string[]

Or an inline version that only works with strings:

const d2 = data.map(d => d.id).filter((o): o is string => !!o);

Playground link

Upvotes: 9

Related Questions