Utilizing Array.every to refine a union of array types, narrowing down the options

I need to narrow down the type of a variable that is a union of different array types in order to run specific code for each type. I attempted to use Array.every along with a custom type guard, but encountered an error in TypeScript stating "This expression is not callable," with a confusing explanation.

Here is a simplified example:

const isNumber = (val: unknown): val is number => typeof val === 'number';

const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];

if (unionArr.every(isNumber)) { // <- Error
  unionArr;
}

TypeScript Playground

The error message reads:

This expression is not callable.
  Each member of the union type
    '{
      <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[];
      (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean;
    } | {
      ...;
    }'
  has signatures, but none of those signatures are compatible with each other.

To resolve this issue, I found that using a type assertion to convert my array to unknown[] before narrowing its type, or changing the array from string[] | number[] to (string | number)[] helped eliminate the error without compromising type safety.

However, even after these adjustments, further type assertions were needed to correctly narrow the array type:

const isNumber = (val: unknown): val is number => typeof val === 'number';

const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];

if ((unionArr as unknown[]).every(isNumber)) { // <- No error
  unionArr; // <- Incorrectly typed as string[] | number[]
}

if ((unionArr as (string | number)[]).every(isNumber)) { // <- No error
  unionArr; // <- Type incorrectly remains as string[] | number[]
}

TypeScript Playground

A similar comparison with a non-array union did not yield any errors and successfully narrowed the type:

const isNumber = (val: unknown): val is number => typeof val === 'number';

const union: string | number = Math.random() > 0.5 ? 1 : '1';

if (isNumber(union)) {
  union; // <- Correctly typed as number
}

TypeScript Playground

While the workaround works fine, the root cause of the error still remains unclear. It seems to be related to how TypeScript interprets the typings of Array.every, and despite trying various approaches, the type assertion as unknown[] seems to be the most suitable solution. If there are better alternatives or if something could be done differently, please feel free to share your insights!

Answer №1

Let's consider a more straightforward example:

type StringOrNumberFunction = (input: string | number) => void
type NumberOrBooleanFunction = (input: number | boolean) => void
declare const justValue: StringOrNumberFunction | NumberOrBooleanFunction

// works
justValue(123)

// errors
justValue('abc')
justValue(true)

When trying to invoke a union of functions, you end up calling a function that is the intersection of those members. If unsure about the function being called, only methods supported by both functions can be used. In this scenario, since both functions accept a number, only numbers are allowed.

If the intersection of these functions would have incompatible arguments, calling the function becomes impossible. Let's illustrate this with an example:

type StringType = (value: string) => void
type NumberType = (value: number) => void
declare const functionUnion: StringType | NumberType

functionUnion(123)    // Argument of type 'number' is not assignable to parameter of type 'never'.(2345)
functionUnion('hello') // Argument of type 'string' is not assignable to parameter of type 'never'.(2345)

This closely resembles your issue.

Playground


The every method for string arrays and number arrays expects different parameters.

(value: string, index: number, array: string[]) // string[] every() args
(value: number, index: number, array: number[]) // number[] every() args

Which presents a similar challenge as discussed above.


Hence, it seems unlikely that the compiler will permit calling the every method on this union.

In such cases, I would suggest performing a type assertion for the entire array and manually iterating over it.

const isArrayOfTypeNumber = (array: unknown[]): array is number[] => {
  for (const value of array) {
    if (typeof value !== 'number') return false
  }

  return true
}

declare const unitedArray: string[] | number[]

if (isArrayOfTypeNumber(unitedArray)) {
  Math.round(unitedArray[0]); // This will work
}

Playground

Answer №2

Latest Update for July 2023

A new feature in TypeScript 5.2, which is currently being tested in beta version, introduces a change in how TypeScript handles function calls with union types, particularly when the function is related to array methods like the one mentioned in my previous query.

To learn more about this specific update, visit: Simplifying Method Usage for Union Types of Arrays

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The TypeScript error "Issue with Type Assertion: 'This expression is not callable Type'...' has no call signatures" occurs when there is a missing semicolon

Here's a simplified version of our original code: const start: number = 10 const end: number = 20 (someElement as HTMLInputElement).setSelectionRange(start, end) We encountered an error with the 20, where a red squiggly line appeared indicating ...

TypeScript: implementing function overloading in an interface by extending another interface

I'm currently developing a Capacitor plugin and I'm in the process of defining possible event listeners for it. Previously, all the possible event listeners were included in one large interface within the same file: export interface Plugin { ...

Synchronization problem encountered in an Angular app involving playwright

Currently, I am working on automating a process where the service queries the database and displays the data on the user interface. However, the rendering takes a considerable amount of time, around 3 minutes. Despite using Playwright for automation, it do ...

Reducing the size of associative PHP arrays by compacting them

Given the array below in PHP: array('red','blue','green','purple'); I am looking for a simple and quick way to transform that array into this format: array('red'=>'blue','green'=&g ...

Using Angular to include more than two parameters in an HTTP GET request

Currently, I am developing an application that requires downloading a file upon clicking a button within a template. The screen displays multiple files, each with its own corresponding button. I need to send the index number of the array to Angular and pas ...

Angular: bypassSecurityTrustHtml sanitizer does not render the attribute (click)

I'm facing an issue where a button I rendered is not executing the alertWindow function when clicked. Can anyone help?: app.component.ts: import { Component, ElementRef, OnInit, ViewEncapsulation } from '@angular/core'; import ...

Encountering build errors while utilizing strict mode in tsconfig for Spring-Flo, JointJS, and CodeMirror

While running ng serve with strict mode enabled in the tsconfig.json, Spring-Flow dependencies are causing errors related to code-mirror and Model. Any suggestions on how to resolve this issue? ...

I am facing an issue with the Angular2 Modal Form where it only displays the data but does

Hey there, I recently started diving into Angular and I'm loving the learning process. Currently, I've managed to successfully load a form into my Modal when clicking on "viewDetails". However, as soon as I modify the Form from <form ngNoFo ...

Generate random entries from an object based on specific criteria and append them to a fresh object using Typescript

Experimenting with Ionic2 and Typescript has been my recent focus. I have an object that contains various meals, calorie counts, and meal types (such as vegan). This is how the object looks: [ { "id":14093, "name":"Proteinshake mit Wasser ...

Encountering an error when using the Vue 3 TypeScript Composition API for style binding with an asynchronous

I utilized nexttick alongside an async method to update a DOM element. However, I am encountering issues with returning the correct style type. An error message pops up stating: error TS2322: Type 'Promise<{ maxHeight: string; }>' is not ...

activeStyle is not a valid property for type 'IntrinsicAttributes'

I encountered an issue while attempting to utilize NavLink in a react typescript project. The error message states: "Property 'activeStyle' does not exist on type 'IntrinsicAttributes & NavLinkProps & RefAttributes'." import Rea ...

Identifying Matching Array Indexes

Received a POST request on one of my pages, below is an excerpt: [shipCountry] => United States [status] => Accepted [sku1] => test [product1] => Test Product [quantity1] => 1 [price1] => 0.00 The request may vary in size, with product ...

Struggling to access the properties of a Material-UI Button

import * as React from "react"; import { styled } from "@mui/material/styles"; import MuiButton from "@mui/material/Button"; import Slider from "@mui/material/Slider"; interface Props { type: "primary" | ...

Creating a function that can have either one or two arguments, with the types of the arguments determined by a specific string literal

I am looking to create a function called emitEvent(event, extra?), which will be restricted by a string literal enum of known strings such as POPUP_OPEN and POPUP_CLOSED. The function will accept a second argument that is a specifically defined dictionary ...

Tips for changing array items into an object using JavaScript

I am working with a list of arrays. let arr = ["one","two"] This is the code I am currently using: arr.map(item=>{ item }) I am trying to transform the array into an array of sub-arrays [ { "one": [{ ...

I am interested in creating a class that will produce functions as its instances

Looking to create a TypeScript class with instances that act as functions? More specifically, each function in the class should return an HTMLelement. Here's an example of what I'm aiming for: function generateDiv() { const div = document.crea ...

Error in Nestjs Swagger: UnhandledPromiseRejectionWarning - The property `prototype` cannot be destructed from an 'undefined' or 'null' object

Currently, I am in the process of developing a Nestjs REST API project and need to integrate swagger. For reference, I followed this repository: https://github.com/nestjs/nest/tree/master/sample/11-swagger However, during the setup, I encountered the foll ...

How to retrieve a specific property from a nested array within a multidimensional array in PHP

I am currently iterating over one array with the following structure: array:8132 [ 0 => {#551 "address_id": "94e224af-135f-af31-3619-535acfae9930" "fiber_phase": "101" "parsed_hash": "1bc7fb114ee10d7cb9cea10693d238b5" "min_number": 400 "max ...

Guide on organizing the Object into a precise structure in Angular

I am looking to transform the current API response object into a more structured format. Current Output let temp = [ { "imagePath": "", "imageDescription": [ { "language": "en ...

The new data is not being fetched before *ngFor is updating

In the process of developing a "Meeting List" feature that allows users to create new meetings and join existing ones. My technology stack includes: FrontEnd: Angular API: Firebase Cloud Functions DB: Firebase realtime DB To display the list of meeting ...