Is there a way to create a TypeScript function that can accept both mutable and immutable arrays as arguments?

Writing the following method became quite complicated for me.

The challenge arose because any method receiving the result from catchUndefinedList now needs to handle both mutable and immutable arrays.

Could someone offer some assistance?

/**
 * Catch any errors with a list.
 */
export function catchUndefinedList<T> (list: readonly T[] | T[]): readonly T[] | T[] {
  return nullOrUndefined(list) || !list?.length ? [] : list
}

EDIT: added nullOrUndefined

export function nullOrUndefined (el: any): el is (undefined | null) {
  return typeof el === 'undefined' || el === null
}

EDIT: added an example to illustrate the issue.

In the example, catchUndefinedList takes in a parameter list1, which is a mutable array. The output will be an immutable array, even though list1 was mutable, as catchUndefinedList simply returns the parameter list when it's not undefined.

Attempting to push to list2 will fail due to immutability, resulting in TS2339 error.

const list1 = ['foo']
const list2 = catchUndefinedList(list1)
list2.push('bar')

Answer №1

Initially, the context of this question only holds significance if the type for list allows null or undefined. Assuming you intended to include those options.


Your objective seems to be having the function return an array with the same type as the input. Therefore, your function should be generic on the array itself rather than just its member type. This distinction is crucial because the readonly characteristic resides in the array type, not its members.

A potential implementation could resemble the following:

export function catchUndefinedList<
  T extends readonly unknown[]
> (list: T | null | undefined): T {
  return (
    nullOrUndefined(list) ||
    !list?.length
      ? [] // Type 'T | never[]' is not assignable to type 'T'.
      : list
  )
}

// mutable
const list1 = ['foo']
const list2 = catchUndefinedList(list1)
list2.push('bar')

// immutable
const immlist1: readonly string[] = ['foo']
const immlist2 = catchUndefinedList(immlist1)
immlist2.push('bar') // Property 'push' does not exist on type 'readonly string[]'.(2339)

The code snippet demonstrates that pushing to a mutable array is permitted while attempting to do so on an immutable array results in an error, which is desired behavior.


However, there arises an issue with tuples. This is the reason behind the type error present in the above code excerpt. Imagine invoking the function like this:

const tuple2 = catchUndefinedList<[string, number, boolean]>(undefined)
tuple2[0].split('') // no type error, instead there is a runtime error

The problem stems from your function returning [] in the undefined scenario, which is not valid for a tuple.

You might manage to silence the error by using [] as unknown as T, but such an approach is discouraged as it might lead to runtime errors in unexpected situations.

It's challenging to properly constrain this situation since all tuples are subtypes of unbounded arrays.

Playground


Despite that, this complexity may seem excessive for a simple null check. Are you certain this is the direction you wish to pursue?

In essence, the provided code performs the following tasks:

  • If list is null or undefined, it returns a new empty array
  • If list is an empty array, it returns a new empty array
  • If list contains one or more items, it returns list.

This functionality appears similar to utilizing the Nullish coalescing operator ??:

list ?? []

The primary distinction between your code and this line is that when list is empty, it returns list instead of creating a new empty array. However, since both cases result in an empty array, this divergence most likely won't be significant.

Nonetheless, the Nullish coalescing operator behaves sensibly across scenarios:

// mutable
const list1 = ['foo'] as string[] | undefined
const list2 = list1 ?? []
list2.push('bar')

// immutable
const immlist1 = ['foo'] as readonly string[] | undefined
const immlist2 = immlist1 ?? []
immlist2.push('bar') // type error

// tuple
const tuple1 = ['a', 1] as [string, number] | undefined
const tuple2 = tuple1 ?? []
tuple2.push('c') // type error

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

cssclassName={ validatorState === RIGHT ? 'valid' : 'invalid' }

Is there a way to dynamically add different classes based on validation outcomes in React? My current implementation looks like this: className={ validatorState === RIGHT ? 'ok' : 'no' } However, I also need to handle cases where the ...

What is the best way to update multiple data tables using TypeScript?

In my Angular 7 project, I am utilizing TypeScript to implement two data tables on a single page. Each table requires a rerender method in order to incorporate a search bar functionality. While the data tables come with built-in search bars, the sheer volu ...

Linking typescript error messages with their respective compiler configurations (tsconfig.json)

Is there a way to identify which compiler option corresponds to a specific Typescript error? While working with Typescript in VSCode, I often encounter errors like initializer provides no value for this binding element. (Please note that these are warnin ...

Unable to sign out user from the server side using Next.js and Supabase

Is there a way to log out a user on the server side using Supabase as the authentication provider? I initially thought that simply calling this function would work: export const getServerSideProps: GetServerSideProps = withPageAuth({ redirectTo: &apos ...

Excluding a common attribute from a combined type of objects can lead to issues when accessing non-common attributes (TypeScript)

In the process of developing a wrapper function, I am passing a refs property into a send function. The Event type used to construct my state machine is defined as an intersection between a base interface { refs: NodeRefs } and a union of possible event ob ...

The HTTP DELETE request encountered a TypeError, stating that error.json is not a valid function

Within my Angular application, there is a feature that involves a "delete button". When this button is clicked, a confirmation popup appears asking the user if they are certain they want to proceed with the deletion. If the user confirms by clicking ' ...

Guide to specifying a type as optional based on specific criteria

In coding, there exists a certain type that is defined as follows: type PropsType = { dellSelectedOption: (id: string, idOptions: string[]) => void; ownFilterData: Array<ActiveFilterAndPredFilterDataType>; watchOverflow: boolean; childre ...

When trying to access the DOM from another module in nwjs, it appears to be empty

When working with modules in my nwjs application that utilize document, it appears that they are unable to access the DOM of the main page correctly. Below is a simple test demonstrating this issue. The following files are involved: package.json ... "ma ...

In the production build, the RegEx validation is lacking and fails to accept certain characters like 0, 2, 7, a, c, u, x, and occasionally z

Incorporating Angular 15.2.10 and Typescript 4.9.5, the RegEx utilized in one of my libraries and exposed via a service is outlined as follows: private readonly _DISALLOWED_CHARS_REGEX_GENERAL = new RegExp(/^[^\\/\?\!\&\: ...

Beware: React higher-order component alert - Caution: It is not possible to modify a component from within the function body of another

Recently, I crafted a simple higher-order component that retrieves data from an API endpoint and handles loading, error messages, or displaying content upon success. Although the component functions correctly, it triggers the following error message: War ...

Encountering an Issue: The formGroup function requires an instance of a FormGroup. Kindly provide one

I am a beginner with Angular 2 and despite reviewing numerous stack overflow answers, I still can't resolve my issue. I have recently started learning about angular reactive forms and wanted to try out my first example but I'm facing some diffic ...

What is the recommended data type for Material UI Icons when being passed as props?

What specific type should I use when passing Material UI Icons as props to a component? import {OverridableComponent} from "@mui/material/OverridableComponent"; import {SvgIconTypeMap} from "@mui/material"; interface IconButtonProps { ...

Exploring TypeScript: Navigating the static methods within a generic class

I am trying to work with an abstract class in TypeScript, which includes an enum and a method: export enum SingularPluralForm { SINGULAR, PLURAL }; export abstract class Dog { // ... } Now, I have created a subclass that extends the abstract cla ...

The error message "Identifier 'title' is not defined. '{}' does not contain such a member angular 8" indicates that the title variable is not recognized or defined in the

Here is the code snippet of my component: import { Router, ActivatedRoute } from '@angular/router'; import { Component, OnInit } from '@angular/core'; import { CategoriesService } from 'src/app/categories.service'; import { P ...

Combining indexed types with template literals -- add a prefix to each key

Start with type A and transform it into type B by adding the prefix x to each key using Typescript's newest Template Literal Types feature: type A = { a: string; b: string; }; // Automatically generate this. type Prefixed = { xa: string; xb: ...

Encountering an issue post-upgrade with Angular 7 project

Currently, I am facing an issue with upgrading a project from Angular 6 to version 7. Despite following multiple online tutorials and successfully completing the upgrade process, I encountered an error when running the 'ng serve' command: ERROR ...

When utilizing the navigation.navigate function, react-navigation v6.0 may present an error message

The Challenge I'm Dealing With One issue I encountered is when I use navigation.navigate('XXXPage'), react-navigation version 6.0 displays the following error message. Argument of type 'string' is not assignable to parameter of ty ...

Every row within the data grid component must contain a distinct `id` property

How do I utilize statId instead of id in my code? Should I create a unique id property for each row? Error: MUI: All rows in the data grid component must have a distinct id property. Alternatively, you can define a custom id using the getRowId prop. A ...

"Unexpected compatibility issues arise when using TypeScript with React, causing errors in props functionality

Just the other day, my TypeScript+React project was running smoothly. But now, without making any changes to the configurations, everything seems to be broken. Even rolling back to previous versions using Git or reinstalling packages with NPM does not solv ...

How to Measure the Length of an Undefined Value in Jasmine Angular Unit Tests

Here's a function that I have: updateParts(enviromentContainsAllParts: PartsContainsAllParts): Observable<boolean> { const enviroment = cloneDeep(this.enviroment); enviroment.containsAllPart = enviromentContainsAllParts.containsAllPart ...