Using TypeScript's Array Union Type in defining function parameters

My scenario involves a union type of an Array with specific lengths:

[ number ] | [ number, number ] | [ number, number, number, number ]

The requirements are for an array with either one element, two elements, or four elements.

I am attempting to create an object that contains a function with one of these lengths. How should I write the type definition to accommodate this?

TS playground

For example:

const people: {
    name: string,
    address: Address,
    work: (numbers: [ number ] | [ number, number ] | [ number, number, number, number ]) => any
}[] = [
    {
        name: "Bob",
        address: new Address(),
        work: function(numbers: [ number ]): number {
            // Implementation returning number
        }
    },
    {
        name: "Ashley",
        address: new Address(),
        work: function(numbers: [ number, number, number, number ]): boolean {
            // Implementation returning boolean
        }
    },
    {
        name: "Michael",
        address: new Address(),
        work: function(numbers: [ number, number ]): number {
            // Implementation returning number
        }
    },
]

Currently, I am encountering this error:

https://i.sstatic.net/y45gY.png

Type '(numbers: [number]) => number' is not assignable to type '(numbers: [number] | [number, number] | [number, number, number, number]) => any'. Types of parameters 'numbers' and 'numbers' are incompatible. Type '[number] | [number, number] | [number, number, number, number]' is not assignable to type '[number]'. Type '[number, number]' is not assignable to type '[number]'. Source has 2 element(s) but target allows only 1.ts(2322)

------- Edit -------

Following a suggestion from the comments, I have separated all the possible function calls into distinct function unions instead of an array union:

const people: {
    name: string,
    address: Address,
    work: ((numbers: [ number ]) => any) | ((numbers: [ number, number ]) => any) | ((numbers: [ number, number, number, number ]) => any)
}[] = [

Now, when attempting to call a function from this array:

 people[1].work([2, 8, 6, 4])

An error is now thrown:

https://i.sstatic.net/eyZW2.png

In VSCode, I discovered the reason for this:

"The intersection '[number] & [number, number] & [number, number, number, number]' was reduced to 'never' because property 'length' has conflicting types in some constituents."

Answer №1

UPDATED Utilizing bivariance is necessary in this scenario

class Address { }

type Tuple<
  N extends number,
  Item = number,
  Result extends Array<unknown> = [],
  > =
  (Result['length'] extends N
    ? Result
    : Tuple<N, Item, [...Result, number]>
  )


interface WorkFn {
  work(numbers: Tuple<1> | Tuple<2> | Tuple<4>): any
}

interface Person extends WorkFn {
  name: string,
  address: Address,
}

const people: Person[] = [
  {
    name: "Bob",
    address: new Address(),
    work(numbers: Tuple<1>) {
      const [myNumber] = numbers;

      return myNumber * 6
    }
  },
  {
    name: "Ashley",
    address: new Address(),
    work: function (numbers: Tuple<4>): boolean {
      const [myNumber, anotherNumber, someNumber, replaceNumber] = numbers;

      return myNumber === anotherNumber && someNumber === replaceNumber;
    }
  },
  {
    name: "Michael",
    address: new Address(),
    work: function (numbers: Tuple<2>): number {
      const [myNumber, anotherNumber] = numbers;

      return myNumber * anotherNumber;
    }
  },
]

Explore this TypeScript playground

Learn more about the variance and types in TypeScript here

It should be noted that this approach may not be completely foolproof

Answer №2

One suggestion I have is to utilize a generic function for generating the people array.

function createPeople<
  T extends {
    name: string,
    address: Address,
    work: ((numbers: [ number ]) => any) | ((numbers: [ number, number ]) => any) | ((numbers: [number, number, number, number ]) => any)
  }[]
>(p: [...T]){
  return p
}

const people = createPeople([
  {
    name: "Bob",
    address: new Address(),
    work: function(numbers: [ number ]): number {
      const [ myNumber ]: [ number ] = numbers;

      return myNumber * 6
    }
  },
  {
    name: "Ashley",
    address: new Address(),
    work: function(numbers: [ number, number, number, number ]): boolean {
      const [ myNumber, anotherNumber, someNumber, replaceNumber ]: [ number, number, number, number ]= numbers;

      return myNumber === anotherNumber && someNumber === replaceNumber;
    }
  },
  {
    name: "Michael",
    address: new Address(),
    work: function(numbers: [ number, number ]): number {
      const [ myNumber, anotherNumber ]: [ number, number ] = numbers;

      return myNumber * anotherNumber;
    }
  },
])

The benefit of this approach is that TypeScript can now infer the callback type for each index, ensuring strict typing for all function calls.

people[1].work([2, 8, 6, 4])
people[2].work([1, 2])

Playground

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 Angular 2 UI is unable to successfully connect with the controller through the API call

When attempting to call a URL from Angular 2 using http.get(), an exception is being thrown and the debugger in the controller is not being hit. Although I have checked the proxy and routing, they are the same as my existing projects. This new project is c ...

Employing strict mode, albeit with certain exceptions

When using the compiler strict mode ("strict": true), errors occur for my models that are structured like this: @Entity class MyModel { @Column() public name: string; @Column() public email: string; ... } The specific errors enc ...

Retrieve the runtime configuration object or file using Jest

Is there a way to access the Jest runtime config object or file path? I wanted to utilize runtime config properties in my custom matchers. // ./jest.config.js const path = require("path"); module.exports = { prop1: "foo", prop2: "bar" }; // my-custo ...

Encountering "Invalid hook call" error with React Router while integrating Higher Order Components for authentication

Dealing with an error: React Router shows "Invalid hook call" with higher-order components for authentication Dilemma I have developed two distinct approaches for authentication wrappers in my React components with React Router. The first method functions ...

Issue - The path to the 'fs' module cannot be resolved in ./node_modules/busboy/lib/main.js

After adding a new React component to my NextJS app, I encountered a mysterious error in my local development environment: wait - compiling... error - ./node_modules/busboy/lib/main.js:1:0 Module not found: Can't resolve 'fs' null Interest ...

Retrieve the final variable in an Observable sequence

In my code, I have a variable called 'messages' which stores messages from a conversation: messages: Observable<Message[]>; To populate the 'messages' variable, I do the following: const newMessage = new Message(objMessage); ne ...

Issues with loading Angular 9 application on Internet Explorer 11

Having trouble with my app not loading in IE 11 after adding ngx-treeview. Encountering the error (SCRIPT1002: Syntax error), Script Error Error point in vendor.js Unsure how to resolve this issue. Works fine in chrome and firefox, but in IE11 all I se ...

Testing Slack's Web API with Jest for mock purposes

Currently, I am working with two files. One file is where I set up a slack web API client to post a message and test it with a mocked value: main.ts: import { WebClient } from '@slack/web-api'; const slack = new WebClient(process.env.SLACK_API_K ...

The viewport width in NextJS does not extend across the entire screen on mobile devices

I'm currently tackling a challenge with my NextJS Website project. It's the first time this issue has arisen for me. Typically, I set the body width to 100% or 100vw and everything works smoothly. However, upon switching to a mobile device, I not ...

Error message: Unable to assign type (Combining React, Typescript, and Firebase)

Just started using TypeScript and in the process of migrating my React app to incorporate it. Encountering some type issues with Firebase auth service that I can't seem to find a solution for. Any suggestions? import React, { useEffect, useState } f ...

Is it possible to modify the content of an element with a click event in Angular/HTML?

How can I implement a feature in my mat-accordion using mat-expansion-panels where the text becomes underlined when the panels are clicked? I want the title to be underlined when the panels are open and for the underline to disappear when they are closed ...

Show text on a button press in Angular

I have set up a group of three buttons using ngFor, each button has its own unique name stored in an array called buttonData[]. The buttonData array also includes corresponding text and images for each button. My goal is to display a specific text and imag ...

"Troubleshooting issues with data loading using React's useEffect function

While working on my project, I encountered a strange issue where the isLoading state is being set to false before the data fetching process is completed. I am using the useEffect hook to show a loading spinner while fetching data from an API, and then disp ...

Navigate back to the main page using a tab

Is there a way to navigate back to the rootPage that is defined in the appComponent when using tabs? I have found that the setRoot method does not function as I anticipated. When used on a Tab page, the navigation stack is not cleared. Instead of the navig ...

Updating a global variable in Ionic 3

I recently started exploring the world of Ionic, Angular, and TypeScript. I encountered a scenario where I needed to have a variable "ar: AR" accessible across all pages. To achieve this, I decided to make it a global variable by following these steps: Fi ...

Is there a way to eliminate duplicate elements from 2 arrays in Angular?

Imagine I have a scenario with two arrays: arr1 = ["Tom","Harry","Patrick"] arr2 = ["Miguel","Harry","Patrick","Felipe","Mario","Tom"] Is it possible to eliminate the duplicate elements in these arrays? The desired output would be: arr2 = ["Miguel"," ...

Oops! The react-social-media-embed encountered a TypeError because it tried to extend a value that is either undefined, not a

I recently attempted to incorporate the "react-social-media-embed" package into my Next.js project using TypeScript. Here's what I did: npm i react-social-media-embed Here is a snippet from my page.tsx: import { InstagramEmbed } from 'react-soc ...

Angularfire2 retrieve list of data with a specified number of items from the

I am facing a challenge in retrieving a specific node from my firebase database. https://i.sstatic.net/YDevB.png The technologies I am using include: "angularfire2": "^5.0.0-rc.4", "firebase": "^4.9.0", In my component code, you can find the following ...

A comprehensive guide to using Reactive Forms in Angular

I need help understanding how FormGroup, FormControl, FormArray work in Angular. The error message I'm encountering is: Type '{ question: FormControl; multi: true; choices: FormArray; }' is not assignable to type 'AbstractControl' ...

Executing a function in Angular depending on the values emitted by two distinct observables

As someone who is relatively new to Angular (6 months), I am facing a challenge with my code. Currently, I have two observables that are working independently of each other: this.siteService.siteChanged$ .pipe(takeUntil(this.disconnect$)) .subscribe(_ ...