Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript union types don't work with const assertions ('as const') [duplicate]

i get the no compatible call signatures error and dont know what to do to make it work.

React-State

     this.state = {
    categoryState: ...,
    categoryItemId
    }

Category declaration

let category: Cat1[] | Cat2[] | Cat3[] = this.getCategory();

getMethod to return category

private getCategory() {
        switch (this.state.categoryState) {
            case "Cat1": {
                return this.props.cat1 as Cat1[];
            }
            case "EVENT": {
                return this.props.cat2 as Cat2[];
            }
            default: {
                return this.props.cat3 as Cat3[];
            }
        }
    }

return-Method (react) this is where the error happens:

<select value={this.state.categoryItemID} onChange={this.handleSomething}>
                                {(category && category.length > 0) &&
                                category.map((item: any) => {
                                    return (
                                        <option value={item.id}
                                                key={item.id}>{item.localizations[this.props.currentLanguage].title}</option>
                                    );
                                })
                                }
                            </select>

error message: TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((callbackfn: (value: Cat1, index: number, array: Cat1[]) => U, thisArg?: any) => U[] |...' has no compatible call signatures.

like image 564
sleepysimon Avatar asked Dec 13 '25 09:12

sleepysimon


1 Answers

Update 2019-03: With the release of TypeScript 3.3, there has there has been progress for calling a union of function types, but unfortunately this pain point still exists for generic methods like Array.prototype.map():


This is a pain point in TypeScript: there's no way to call a "union of function types" unless either they accept exactly the same arguments, or starting in TS3.3, if at most one element of the union of functions is generic or overloaded. In your case, since category is Cat1[] | Cat2[] | Cat3[], the method category.map() is itself a union of generic functions whose parameters are not the same; and you can't call that. It should be safe to call category.map(f) if f is a function which accepts Cat1 | Cat2 | Cat3 (this may seem obvious, but involves understanding contravariance of method arguments, which is easy to get mixed up). But, as the linked issue mentions, the compiler doesn't let you do that:

This is currently by design because we don't synthesize an intersectional call signature when getting the members of a union type -- only call signatures which are identical appear on the unioned type.

So you get an error. Luckily there are workarounds. The easiest in your case is to treat category not as Cat1[] | Cat2[] | Cat3[], but as (Cat1 | Cat2 | Cat3)[]. As long as you're just reading from the array and not writing to it, it should be safe to do that:

let category: Cat1[] | Cat2[] | Cat3[] = this.getCategory();
const mappableCategory = category as (Cat1 | Cat2 | Cat3)[];

Now you should be able to map over mappableCategory instead of category:

mappableCategory.map((item: any) => { /* ... */ }); // should work now.

(Just to reiterate: reading from mappableCategory is fine, but writing to mappableCategory could be a mistake, since it will happily accept elements of Cat1, even if the underlying category was an array of Cat2. So don't write to it unless you're sure what you're doing.)

Hope that helps you. Good luck!

like image 189
jcalz Avatar answered Dec 15 '25 04:12

jcalz