Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Argument of type 'number' is not assignable to parameter of type 'never' in array.includes()

I'm receiving a Typescript error,

error TS2345: Argument of type 'number' is not assignable to parameter of type 'string & number'. Type 'number' is not assignable to type 'string'. 112

setProductPrices(productPrices.filter(
  (p): boolean => !selectedProductIds.includes(p.id)));
                                               ^^^^

from the following code:

export interface IProductPrice {
    id: number;
}
const [productPrices, setProductPrices] = useState<IProductPrice[]>([]);
const [selectedProductIds, setSelectedProductIds] = useState<string[] | number[]>([]);

const deleteSelectedProducts = (): void => {
        setProductPrices(productPrices.filter((p): boolean => !selectedProductIds.includes(p.id)));
        setSelectedProductIds([]);
};

The includes() method is supposedly expecting a parameter of 'never' while p.id is a number. Does anyone know how to fix this?

like image 680
Erin Avatar asked Feb 14 '20 22:02

Erin


People also ask

Is not assignable to parameter of type to?

The error "Argument of type string | undefined is not assignable to parameter of type string" occurs when a possibly undefined value is passed to a function that expects a string . To solve the error, use a type guard to verify the value is a string before passing it to the function.

Is not assignable to type never []' ts 2322?

ts(2322) arr[0] = 'a'; We declared an empty array and it got assigned a type of never[] . This type represents an array that will never contain any elements (will always be empty). To solve this, we have to explicitly type the empty array.

What is never [] type?

The never type represents the type of values that never occur. For instance, never is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns. Variables also acquire the type never when narrowed by any type guards that can never be true.

Is not assignable to parameter of type never useState?

The error "Type is not assignable to type 'never'" occurs when we declare an empty state array with the useState hook but don't type the array. To solve the error, use a generic to type the state array, e.g. const [arr, setArr] = useState<string[]>([]) . Here is an example of how the error occurs.


2 Answers

The Problem

When you have a union of arrays of different types, you can't call methods on them.

The Reason

You have string[] | number[], and so .filter is:

  ((filterFunc: (x: string) => boolean) => string[]) 
| ((filterFunc: (x: number) => boolean) => number[])

When TS combines those signatures, it's going to union the two filterFunc signatures together:

  ((x: number) => boolean) 
| ((x: string) => boolean)

This is a little unintuitive, but this simplifies to (x: number & string) => boolean. Because if you have a function that take X, or a function that takes Y, the only safe thing to pass is something that is both X and Y, or X & Y.

number & string however, is an impossible type, which "simplifies" to never. Hence why the signature is (x: never) => boolean.

The Fix

Ideally you'd only use string[] or number[] but not both. (Just looking at the code, it's a little mysterious why id can only be number, but "selected product ids" can be strings, too)


But if you do need to support both strings and numbers, the easiest fix here is to use Array<string | number> instead of string[] | number[]: the single array type doesn't have the issue of trying to union together two .filter signatures.

You can either change your state to be of that type:

const [selectedProductIds, setSelectedProductIds] = useState<Array<string | number>>([]);

This is simple, but has the downside that it will allow arrays of mixed strings and numbers, which may not be desirable. (e.g. setSelectProductIds(['0', 1, '2'])


If not, you can temporarily cast to Array<string | number>, do the filter, then cast back to string[] | number[]. This is not super clean, but should be safe:

setProductPrices(
    (productPrices as Array<string | number>).filter((p): boolean => !selectedProductIds.includes(p.id)) as (string[] | number[])
);
like image 190
Retsam Avatar answered Nov 15 '22 10:11

Retsam


For your useState instead of

const [selectedProductIds, setSelectedProductIds] = useState<string[] | number[]>([]);

Can you try

const [selectedProductIds, setSelectedProductIds] = useState< (string|number)[]>([]);

instead?

This is because there is probably some missing code where you've set selectedProductIds as an array of string. And in your code you've strictly defined it as 1 array and not the other. Maybe the above change fixes that. Kindly confirm.

like image 37
Y M Avatar answered Nov 15 '22 09:11

Y M