Tips for incorporating conditional types into function parameters based on existing input

The title might be confusing, so allow me to clarify.

My Objective
I am referring to the code snippet below. I aim to specify the route property based on the types property as either a string or a function that returns a string.

The Code
Let's begin with a functional example.

// Utilizing TypeScript to define types without actual values provided
const defineArgTypes = <
  T extends {
    args?: ArgumentsBase | null;
  }
>() => null as any as T;

interface ArgumentsBase {
  queryParams?: Record<string | number | symbol, any>;
}

type BaseActionMap = Record<
  string,
  {
    types?: { args?: ArgumentsBase };
  }
>;

type ActionMapItem<Item extends BaseActionMap[string]> = {
  types?: Item['types'];
};

type ActionMap<BaseConfig extends BaseActionMap> = {
  [Property in keyof BaseConfig]: ActionMapItem<BaseConfig[Property]>;
};

type ConfigMapItem<Item extends BaseActionMap> = {
  route: Item['types'] extends { args: ArgumentsBase }
    ? (args: Item['types']['args']['queryParams']) => string
    : string;
};

type ConfigMap<AMap extends ActionMap<any>> = {
  [Property in keyof AMap]: ConfigMapItem<AMap[Property]>;
};

const defineActions = <Data extends BaseActionMap>(
  actions: Data,
  config: ConfigMap<Data>
) => {
  // irrelevant code nested here
};

const config = defineActions(
  {
    getTodos: {},
    getTodo: {
      types: defineArgTypes<{ args: { queryParams: { id: string } } }>(),
    },
  },
  {
    getTodo: {
      route: (d) => `todos/${d.id}`,
    },
    getTodos: {
      route: 'todos',
    },
  }
);

In the above code, it is necessary to define "actions (getTodos, getTodo)" twice.

Is there a way to streamline this to the following while maintaining conditional types for the route properties?

const config = defineActions(
  {
    getTodos: {
      route: 'todos',
    },
    getTodo: {
      types: defineArgTypes<{ args: { queryParams: { id: string } } }>(),
      route: (d) => `todos/${d.id}`,
    },
  }
);

Answer №1

If you're searching for a solution, consider implementing a discriminated union in Typescript. This type is essentially a combination of two types that share a common property known as the "discriminant." This allows Typescript to narrow down the union based on this property. For instance, here's a simplified example of how you can define a discriminated union:

type Config = {
  types?: undefined
  route: string
} | 
{
  types: {id:string}
  route: (args:{id:string})=>string
}

In this case, the types property acts as the discriminant. If it's undefined, Typescript will infer the first member of the union. If it's {id:string}, Typescript will narrow it down to the second member. You can extend this concept to include more options as needed.

To leverage the typing from types in the route function, you can use generics like so:

type Config<T extends {id: string}> = {
  types?: undefined
  route: string
} | 
{
  types: T
  route: (args:T)=>string
}

After defining your discriminated union, you can use it within your defineActions function by specifying appropriate type parameters. Here's an example:

function defineActions<T extends {id:string}, U extends Record<string,Config<T>>>(configs:U){}

const config = defineActions(
  {
    getTodos: {
      route: 'todos',
    },
    getTodo: {
      types: { id: "myId" },
      route: (d) => `todos/${d.id}`,
    },
  }
);

You can also test this implementation in a playground.

One thing to note: The way TS determines the discriminant may not always be explicit. To ensure proper discrimination based on your intended property (in this case, types), you may need to differentiate other parts of the type definition as well. However, it might not be necessary depending on your specific requirements.

I've provided a simplified explanation tailored to your query. Feel free to ask if you need further clarification or adjustments.

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

Is there a way to retrieve the timezone based on a province or state in TypeScript on the frontend?

I've been working on an angular app that allows users to select a country and state/province. My current challenge is determining the timezone based on the selected state/province, with a focus on Canada and the US for now (expanding to other countrie ...

What is the best way to set a value for a variable that is asynchronous by design?

I'm currently working on an Appium automation framework that is typescript based. The element locator strategy used in this framework is async due to the nature of the plugin I am using, which requires the use of await. However, I encountered some err ...

PhpStorm is unable to resolve the @ionic/angular module

I have encountered a peculiar issue with my Ionic v4 project. While the project runs smoothly, PhpStorm seems unable to locate my references to @ionic. https://i.stack.imgur.com/umFnj.png Interestingly, upon inspecting the code, I realized that it is act ...

Creating React components with TypeScript: Defining components such as Foo and Foo.Bar

I have a react component defined in TypeScript and I would like to export it as an object so that I can add a new component to it. interface IFooProps { title:string } interface IBarProps { content:string } const Foo:React.FC<IFooProps> = ({ ...

Obtain the selected type from a tuple after filtering

I have a tuple with multiple objects stored in it. const repos = [ { name: 'react', type: 'JS' }, { name: 'angular', type: 'TS' }, ] as const const RepoTypes = typeof repos const jsRepoTypes = FilterRepos<&a ...

The assignment of Type Program[] to a string[] is not valid

I am working with a class that contains information about different programs. My goal is to filter out the active and inactive programs, and then retrieve the names of those programs as an array of strings. Below is the structure of the Program class: ex ...

Exporting a Typescript class from one module and importing it into another module

I am encountering issues with my source tree structure, as outlined below: /project/ |- src/ |- moduleA |- index.ts |- classA.ts (which includes a public function called doSomething()) |- moduleB |- classB.ts Th ...

Converting JSON to TypeScript in an Angular project

Within my Angular project, I have an HTTP service that communicates with a web API to retrieve JSON data. However, there is a discrepancy in the naming convention between the key in the JSON response (e.g., "Property" in uppercase) and the corresponding pr ...

I possess a table that showcases MatIcon buttons. Upon clicking on a button, two additional buttons should appear at the bottom of the table

I am working on a table that contains mat-icon-buttons. When the button is clicked, it should display 2 additional buttons at the end of the table. Upon clicking the first button, its color changes from primary to red, and I would like to add two more butt ...

Using TypeScript, you can pass an object property name as a function argument while ensuring the type is

How can I define a type in a function argument that corresponds to one of the object properties with the same type? For instance, if I have an object: type Article = { name: string; quantity: number; priceNet: number; priceGross: number; }; and I ...

Issue with Angular custom tag displaying and running a function

I have created a custom HTML tag. In my next.component.ts file, I defined the following: @Component({ selector: 'nextbutton', template: ` <button (click) = "nextfunc()">Next</button> ` }) export class NextComponent{ nextfunc( ...

What steps should I take to create a React component in Typescript that mimics the functionality of a traditional "if" statement?

I created a basic <If /> function component with React: import React, { ReactElement } from "react"; interface Props { condition: boolean; comment?: any; } export function If(props: React.PropsWithChildren<Props>): ReactElement | nul ...

Adding FormControl dynamically to FormGroup can be achieved by simply using the appropriate method

Currently, I am working with a plunker where I am dynamically creating form components based on the model specified in app.ts. However, I am facing an issue where I cannot add formControlName = "name" to the component. In my control-factory.directive.ts ...

Using 'cy.get' to locate elements in Cypress tutorial

Is there a way to search for one element, and if it's not found, search for another element? cy.get(@firstElement).or(@secondElement).click() Can I use a function similar to || in conditions for this scenario? ...

Setting up NestJs with TypeORM by utilizing environment files

In my setup, I have two different .env files named dev.env and staging.env. My database ORM is typeorm. I am seeking guidance on how to configure typeorm to read the appropriate config file whenever I launch the application. Currently, I am encountering ...

Assembly of these elements

When dealing with a structure where each property is of type These<E, A> where E and A are unique for each property. declare const someStruct: { a1: TH.These<E1, A1>; a2: TH.These<E2, A2>; a3: TH.These<E3, A3>; } I inte ...

Difficulty fetching data on the frontend with Typescript, React, Vite, and Express

I'm currently working on an app utilizing Express in the backend and React in the frontend with typescript. This is also my first time using Vite to build the frontend. While my APIs are functioning correctly, I am facing difficulties fetching data on ...

Using TypeScript's conditional types for assigning types in React

I'm tasked with creating a component that can belong to two different types. Let's call them Type A = { a: SomeCustomType } Type B = { b: SomeOtherDifferentType } Based on my understanding, I can define the type of this component as function C ...

When calling UIComponent.getRouterFor, a TypeScript error is displayed indicating the unsafe return of a value typed as 'any'

I have recently integrated TypeScript into my SAPUI5 project and am encountering issues with the ESLint messages related to types. Consider this simple example: In this snippet of code, I am getting an error message saying "Unsafe return of an any typed ...

Encountering compilation errors while using ng serve in NGCC

After attempting to update peer dependencies, I encountered an issue while compiling my Angular app. The error message displayed: Compiling @angular/material/core : es2015 as esm2015 Compiling @angular/material/expansion : es2015 as esm2015 Compiling @angu ...