Ensuring Proper Typing for Conditional Arrays in Typescript: A Guide

I struggled to find a satisfactory way to define arrays with conditional elements, despite the various methods discussed here. As a result, I decided to simplify the declaration process by creating a helper function. While the helper function itself is straightforward in vanilla JavaScript, I encountered challenges when trying to incorporate generics.

JavaScript version

const nin = Symbol('nin')

const includeIf = (condition, item) =>
    (typeof condition === "function" ? condition(item) : condition) ? item : nin

const conditionalArray = (init) =>
    init(includeIf).filter(item => item !== nin)

/* USAGE */

const cond = false

// should equal ['foo', 'bar', 'qux'] and have type string[]
const arr1 = conditionalArray(addIf => [
    'foo',
    'bar',
    addIf(cond, 'baz'),
    addIf(word => word.length < 10, 'qux')
])

// should equal [{ name: 'Alice', age: 23 }] and have type { name: string, age: number }[]
const arr2 = conditionalArray(addIf => [
    { name: 'Alice', age: 23 },
    addIf(false, { name: 'Bob', age: 34 }),
    addIf(person => person.age > 18, { name: 'Charlie', age: 5 })
])

Updated TypeScript Version with assistance from jcalz

type Narrowable = string | number | boolean | undefined | null | void | {};

const nin = Symbol('nin')

type AddIf = <T, U>(condition: ((x: T) => boolean) | boolean, itemIfTrue: T, itemIfFalse?: U | typeof nin) => T | U | typeof nin
const addIf: AddIf = (condition, itemIfTrue, itemIfFalse = nin) => {
    return (typeof condition === "function" ? condition(itemIfTrue) : condition) ? itemIfTrue : itemIfFalse
}

const conditionalArray = <T extends Narrowable>(init: (addIf: AddIf) => Array<T | typeof nin>) =>
    init(addIf).filter((item): item is T => item !== nin)

Answer №1

Here is the current solution I've come up with:

const nin = Symbol('nin')

// Function to include an item in array based on condition
const includeIf = <T>(condition: boolean | ((x: T) => boolean), item: T) =>
    (typeof condition === "function" ? condition(item) : condition) ? item : nin

// Define the function signature for initialization
type Init<T> = (cb:
    (condition: boolean | ((x: T) => boolean), item: T) => T | typeof nin
) => (T | typeof nin)[]

// Accepts an Init<T>, returns a filtered array of type T[]
export const conditionalArray = <T>(init: Init<T>) =>
    init(includeIf).filter((item: T | typeof nin): item is T => item !== nin)


const cond = true    
declare function generateWord(): string

// Manually specify <string> because inference doesn't work as expected
const arr = conditionalArray<string>(addIf => [
    "foo",
    "bar",
    addIf(cond, "baz"),
    addIf(word => word.length < 10, generateWord())
]);

The typings are correct, but the compiler struggles to infer T from Init<T>. It seems that the nested types are causing confusion. Instead of just calling

conditionalArray(addIf => ...)
, you have to call
conditionalArray<string>(addIf => ...)
to avoid errors and incorrect output types.

I hope this explanation helps resolve any issues you may encounter.


Update: By making the type of init only generic in its return value type, the compiler can now infer properly. Here is the revised code:

const nin = Symbol('nin')

type IncludeIf = typeof includeIf
const includeIf = <T>(condition: ((x: T) => boolean) | boolean, item: T): T | typeof nin => {
    return (typeof condition === "function" ? condition(item) : condition) ? item : nin
}

const conditionalArray = <T>(init: (includeIf: IncludeIf) => Array<T | typeof nin>) =>
    init(includeIf).filter((item): item is T => item !== nin)

If you have any more concerns or questions, please let me know.


Your next issue:

const arr2 = conditionalArray((addIf) => [
  1, 2, 3, addIf(true, 4), addIf(false, '5'), addIf(false, { foo: true })
]); // Array<number | "5" | {foo: boolean}>

Consider whether it's necessary for the compiler to recognize literal values like false in conditions. Real-world scenarios may not require this level of specificity. If needed, utilizing conditional types can address such situations.


UPDATE 2:

To maintain narrow element types in all cases, consider using a Narrowable type constraint:

type Narrowable = string | number | boolean | undefined | null | void | {};

const conditionalArray = <T extends Narrowable>(
    init: (includeIf: IncludeIf) => Array<T | typeof nin>
) => init(includeIf).filter((item): item is T => item !== nin)

const arr1 = conditionalArray(addIf => [1, "a"]);

This approach provides clarity and precision in defining array types with minimal complexity.

Good luck with your implementation! Feel free to reach out if you need further assistance.

Answer №2

A potential TypeScript solution without export/import modules

const placeholder = Symbol('placeholder')

type determineIfIncludeFunc = (criteria: boolean | ((item: string) => boolean), item: string) => Symbol | string;
type addIfCriteriaFunc = (addIfCriteriaFunc: determineIfIncludeFunc) => (Symbol | string)[];
type conditionalArrayFunction = (init: addIfCriteriaFunc) => (Symbol | string)[];

const determineIfInclude: determineIfIncludeFunc = (criteria: boolean | ((item: string) => boolean), item: string): Symbol | string =>
    (typeof criteria === "function" ? criteria(item) : criteria) ? item : placeholder

const conditionalArray: conditionalArrayFunction = (init: addIfCriteriaFunc) =>
    init(determineIfInclude).filter(item => item !== placeholder)

const condition = true

const arrayResult = conditionalArray(addIf =>
    [
        "foo",
        "bar",
        addIf(condition, "baz"),
        addIf(word => word.length < 10, "generateWord")
    ]
)

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

What is the best way to include multiple targets/executables within a single Node.js repository?

My React Native app is developed using TypeScript, and I want to create CLI tools for developers and 'back office' staff also in TypeScript. These tools should be part of the same monorepo. After attempting to do this by creating a subfolder wit ...

Mastering Interpolation in React with TypeScript is essential for creating dynamic and interactive UI components. By leveraging the

Incorporating and distributing CSS objects through ChakraUI presents a simple need. Given that everything is inline, it seems the main issue revolves around "& > div". However, one of the TypeScript (TS) errors highlights an unexpected flagging of ...

What is preventing Apollo from achieving full transformation?

I have been struggling with an issue involving Apollo mutation for the past 2 days. Whenever I call a mutation on Angular Apollo generated code and subscribe to it, the subscription never completes. I am expecting a result from the server, but nothing is ...

What is the best way to utilize v-model with an array of strings in a Vuex store when using v-for

Encountered an issue while trying to set a value in an Array within the Vuex Store: VueCompilerError: v-model cannot be used on v-for or v-slot scope variables because they are not writable. Seeking alternatives to achieve this without creating a local co ...

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 { ...

Issue with binding nested ViewModels/components in Knockoutjs using TypeScript does not resolve

Struggling with implementing a viewModel within another viewModel in knockout. Any assistance would be greatly appreciated. Using typescript and aiming to have a list of address controls, each with their individual viewmodel. Initially, the project functi ...

Is it possible to update the text within a button when hovering over it in Angular?

https://i.sstatic.net/ZhNeM.png https://i.sstatic.net/kb670.png I'm looking to update the text displayed inside a button when hovering over it, similar to the examples in these images. I have already implemented the active state, but now I just need ...

tRPC asynchronous mutation does not have a return value

Attempting to utilize tRPC for the first time here. I have created a mutation called "add" that takes a URL as input and returns a hardcoded slug. Router export const entryRouter = router({ add: publicProcedure .input(input) .output(output) ...

Tips for distinguishing a mapped type using Pick from the original type when every property is optional

I am working with a custom type called ColumnSetting, which is a subset of another type called Column. The original Column type has most properties listed as optional: type ColumnSetting = Pick<Column, 'colId' | 'width' | 'sort ...

Is it possible to set up TypeScript npm packages to be installed in their original TypeScript format rather than JavaScript for the purpose of examining the source code?

Despite my lack of expertise in the inner workings of how a TypeScript library compiles itself to JavaScript before being placed in the node_modules directory, I have a question: Coming from a PHP background, I am accustomed to being able to explore any l ...

What sets typescript apart when using a getter versus a regular function?

Imagine having a class with two methods declared for exclusive use within that class. // 1. private get something() { return 0; } // 2. private getSomething() { return 0; } While I am familiar with getters and setters, I'm intrigued to know if ther ...

Retrieving data from a JSON using Typescript and Angular 2

Here is an example of what my JSON data structure looks like: { "reportSections": [ { "name": "...", "display": true, "nav": false, "reportGroups": { "reports": [ { "name": "...", "ur ...

Can the type of a prop be specified in the parent component before it is passed down to the children components that utilize the same prop?

In my codebase, I have a component called NotFoundGuard. This component only renders its children if a certain condition is true. Otherwise, it displays a generic fallback component to prevent redundancy in our code. I am trying to figure out if there is ...

Sundays and last days are excluding React-big-calendar and dayjs longer events from being displayed

I've encountered a bug in my calendar view implementation. Long events are not displaying on Sundays or the ending day. Please refer to this image for reference: https://i.stack.imgur.com/V0iis.png Event details: Start time: Mon Aug 07 2023 15:44:00 ...

Can you explain the distinction between any[] and [] in TypeScript?

Here is an example that successfully works: protected createGroups(sortedItems: Array<TbpeItem>): any[] { let groups: any[] = []; return groups; } However, the second example encounters a TypeScript error: type any[] not assignable to ...

What method is most effective for combining two JSON files in Angular?

My data includes a json file with a product list that looks like this: [{"id":76, "name":"A", "description":"abc", "price":199, "imageUrl":"image.jpg", "productCategory":[{ "categoryId":5, "category":null },{ "categoryId":6, " ...

Build problems are occurring in the EC2 build box, but they do not arise when building locally

I encountered the following error in my EC2 build environment, but I am unable to reproduce it on my local machine: ERROR in node_modules/rxjs-compat/add/observable/dom/webSocket.d.ts(1,46): error TS2307: Cannot find module 'rxjs/websocket'. In ...

The Rxjs `of` operator fails to emit a value when utilizing proxies

I ran into an issue with a basic unit test that utilizes Substitute.js (I also tried using TypeMoq mocks, but encountered the same behavior). My test simply tries to use the of operator to emit the mocked object. Surprisingly, without any additional opera ...

Unable to loop through the "dataList" retrieved from a service call to the java backend within an Angular 9 application

After receiving JSON data from a Java backend service called houseguidelines, the information is sent to an Angular application via a service call. I am attempting to iterate over this returned JSON data and add it to an array I have created. Unfortunately ...

Efficiently Measuring the Visibility Area of DetailsList in Fluent UI

I am currently utilizing the DetalisList component alongside the ScrollablePane to ensure that the header remains fixed while allowing the rows to scroll. However, I have encountered a challenge as I need to manually specify the height of the scrollable co ...