Automatically deducing types from object keys in Typescript is a convenient feature

I'm looking to define an interface for a Select component that allows for selecting single or multiple items.

interface MySelect<T extends boolean> {
    multi: T, // Indicates if it's a multiple item select
    onChange: (item: T extends true ? string[] : string) => void // The onChange function signature changes based on T
}

It currently works but I have to explicitly specify the generic type T:

const mySelect: MySelect<true> = { // Here
    multi: true, // And here
    onChange: (items) => {}
}

I am curious if TypeScript can automatically infer T from the "multi" value:

const mySelect: MySelect = {
    multi: true, // If multi is true then T should be true
    onChange: (items) => {}
}

Update: I would like "multi" to be optional and default to false if not specified or undefined

Answer №1

Your question has been updated to specify that the multi property should be optional, with a default value of false. This eliminates the possibility of using a discriminated union as outlined in the previous answer below.

In this case, I would recommend using two interfaces that can be combined into a union, along with a base interface for shared properties. Type guard functions will also come in handy when determining the type of the select.

// Common properties for all MySelects (other than `onChange`)
interface MySelectBase {
    name: string;
}

// Interface for single-select MySelect
interface MySingleSelect extends MySelectBase {
    multi?: false;
    onChange: (item: string) => void;
}

// Interface for multi-select MySelect
interface MyMultiSelect extends MySelectBase {
    multi: true;
    onChange: (items: string[]) => void;
}

// Unified type combining both single and multi selects
type MySelect = MySingleSelect | MyMultiSelect;

// Type guard function to check if it's a single select
const isSingleSelect = (select: MySelect): select is MySingleSelect => {
    return !select.multi; // Returns true for !undefined and !false
};

// Type guard function to check if it's a multi select
const isMultiSelect = (select: MySelect): select is MyMultiSelect => {
    return !!select.multi; // Returns true for !!undefined and !!true
};

Examples of creating instances:

const single: MySingleSelect = {
    name: "some-single-select-field",
    onChange : (item) => { console.log(item); }
};

const multi: MyMultiSelect = {
    multi: true,
    name: "some-multi-select-field",
    onChange : (items) => { console.log(items); }
};

Example of utilizing the MySelect interface:

const useMySelect = (select: MySelect) => {
    console.log(select.name);
    const onChange = select.onChange; 
    if (isSingleSelect(select)) {
        const onChange = select.onChange;
    } else {
        const onChange = select.onChange;
    }
};

This original answer caters to scenarios where making the multi property optional is not a requirement:

You can achieve this by defining MySelect as a union of types based on the value of multi, either true or false:

type MySelect =
    {
        multi: true;
        onChange: (items: string[]) => void;
    }
    |
    {
        multi: false;
        onChange: (item: string) => void;
    };

For example:

const mySelect: MySelect = {
    multi: true,
    onChange: (items) => {}
};

This is referred to as a discriminated union, where the union is distinguished by a particular field value.

If there are additional common properties apart from 'multi' and 'onChange', you can include them in the discriminated union using intersections:

type MySelect =
    (
        {
            multi: true;
            onChange: (items: string[]) => void;
        }
        |
        {
            multi: false;
            onChange: (item: string) => void;
        }
    )
    &
    {
        the:        number;
        other:      string;
        properties: string;
    };

Answer №2

If you want to achieve this, a generic identity function can be used:

function customFunction<T extends boolean>(obj: MySelect<T>): MySelect<T> {
    return obj;
}

// Inferred as MySelect<true>
const mySelect = customFunction({
    multi: true,
    onChange: (items) => {}
});

Link to Code Playground

However, this method is more suitable for cases where the type parameter is not limited to specific options; in your scenario, T extends boolean could be better replaced with a tagged union type like in T.J. Crowder's recommendation, excluding generics.

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

Set every attribute inside a Typescript interface as non-mandatory

I have defined an interface within my software: interface Asset { id: string; internal_id: string; usage: number; } This interface is a component of another interface named Post: interface Post { asset: Asset; } In addition, there is an interfa ...

Updating a boolean value when the checkbox is selected

Hey there! I'm currently working on a project using Angular and have a collection of elements that can be checked, which you can check out here. In terms of my business logic: stateChange(event: any, illRecipe: Attendance){ var state: State = { ...

Using TypeScript 4.1, React, and Material-UI, the className attribute does not support the CSSProperties type

Just starting out with Material-UI and we're utilizing the withStyles feature to style our components. Following the guidelines laid out here, I successfully created a classes object with the appropriate types. const classes = createStyles({ main ...

When trying to check a condition in Angular using a boolean, a TypeError is generated stating that a stream was expected instead of the provided 'false' value

Error: In global-error-handler.ts file at line 42, a TypeError has occurred. It states: "You provided 'false' where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable." Below is the s ...

Using React Query's useMutation hook may result in an error message of "No overload matches this call"

When incorporating react-query into my typescript project, I encountered a perplexing type error while attempting to utilize the useMutation() hook with a graphql query. Here is an example of the code: useMutation( async ( parameter1: string, ...

Can someone explain how to create a Function type in Typescript that enforces specific parameters?

Encountering an issue with combineReducers not being strict enough raises uncertainty about how to approach it: interface Action { type: any; } type Reducer<S> = (state: S, action: Action) => S; const reducer: Reducer<string> = (state: ...

The input field cannot accommodate the lengthy value in the Mat Select option

When a user selects a value in my mat select, it doesn't display well in the selection box. The text wraps when the selection is opened, but once a choice is made, it gets cut off without proper spacing between the ellipses and the dropdown arrow. Th ...

Determine the function's return type based on its arguments

Here is the code snippet: const handleNodes = (node: Node | Node[]) => { if (Array.isArray(node)) { return [{}]; } return {}; }; The desired result is: handleNodes([{}]) // infer that this returns an array handleNodes({}) // infer that this r ...

Instructions on changing the color of a full row in the table when the value is missing within the <td> tag. The value is retrieved from an API and iterated through

In this scenario, if the value inside the <tr> tag is null for a cell, then the entire row should be displayed in a different color. The code I have written for this functionality is: <ng-container *ngFor="let row of table?.rows; let rowIndex ...

The initial click may not gather all the information, but the subsequent click will capture all necessary data

Issue with button logging on second click instead of first, skipping object iteration function. I attempted using promises and async await on functions to solve this issue, but without success. // Button Code const btn = document.querySelector("button") ...

RTK update mutation: updating data efficiently without the need to refresh the page

I am facing an issue with my mui rating component in a post-rating scenario. Although the rating updates successfully in the data, the page does not refresh after a click event, and hence, the rating remains enabled. To address this, I have implemented a d ...

Despite attempts to exclude them, types in node_modules continue to be compiled in TypeScript

During my attempt to compile my typescript source code, I've noticed that the compiler is also attempting to compile the types found within my node_modules directory. I am currently utilizing typescript version 2.6.1 and have my tsconfig file set up ...

Error: Missing default export in the imported module "react" according to ESLint

Query import React, { useContext, useEffect, useRef } from 'react'; After enabling esModuleInterop and allowSyntheticDefaultImports in tsconfig.json, using eslint-import-plugin and eslint-import-resolver-typescript for import linting triggers an ...

Troubleshooting an Integration Problem Between Express and socket.io

Having trouble reaching the io.on('connect') callback in my basic express setup. The connection seems to stall. Node 12.14.1 express 4.17.1 socket.io 3.0.1 code import express, { ErrorRequestHandler } from 'express'; import path from ...

Unlocking the Potential of Vue Class Components: Exploring Advanced Customization Options

Currently, I am working on a project using Vue 2 with Typescript. However, I am facing an issue where I cannot add options to the component. <script lang="ts"> import { Component, Vue } from "vue-property-decorator"; import HelloW ...

Should I choose JavaScript or TypeScript for Angular 2.0?

What is the best approach for creating an Angular 2.0 application? Should it be done with JavaScript or TypeScript? I'm struggling to get started with Angular 2.0 using TypeScript, as it's quite different from JavaScript. ...

Alerting Users Before Navigating Away from an Angular Page

I am looking to implement a feature in my app that will display a warning message when attempting to close the tab, exit the page, or reload it. However, I am facing an issue where the warning message is displayed but the page still exits before I can resp ...

Does the method in the superclass "not exist" within this type....?

Our TS application utilizes a JavaScript library, for which we crafted a .d.ts file to integrate it with TypeScript. Initially, the file resided in a "typings" directory within the project and everything operated smoothly. Subsequently, we opted to relocat ...

Transfer an Array of Objects containing images to a POST API using Angular

Looking to send an array of objects (including images) to a POST API using Angular and Express on the backend. Here's the array of objects I have: [{uid: "", image: File, description: "store", price: "800"} {uid: "f ...

One way to incorporate type annotations into your onChange and onClick functions in TypeScript when working with React is by specifying the expected

Recently, I created a component type Properties = { label: string, autoFocus: boolean, onClick: (e: React.ClickEvent<HTMLInputElement>) => void, onChange: (e: React.ChangeEvent<HTMLInputElement>) => void } const InputField = ({ h ...