Struggling with intricate generic type mapping of records in Typescript

Whew...spent an entire day on this. My brain is fried...

I am developing a type-safe mapper function that determines which props are needed based on the mapping type and can predict the output types based on the ReturnType.

However, it seems like each record isn't getting its own type mapping applied individually; instead, all values are being mapped together, resulting in an intersection type of all possible items rather than individual ones.

It's quite tricky to explain, so here's what I have managed to accomplish so far:

Check out a working example in TypeScriptLang Playground here

Answer №1

After some tweaking and experimentation, I managed to come up with a solution that appears to be working partially:

type ActionResult<A extends Action<any>['name']> = { [action in Action<any> as A]: action extends { name: A, type?: { transform: (...args: any) => infer R } } ? R : never; }[A];

export default function useTransformer<IN, E extends keyof IN, M extends Record<keyof M, Action<E>>>(state: IN, mappings: M) {
  type N = keyof M;
  return {
    form: {} as {
      [K in E | N]: K extends N ? ActionResult<M[K]['name']> : (K extends keyof IN ? IN[K] : never);
    },
  };
}

// The form is expected to have the following type:
const form: {
    range: Date[];
    dateFrom: string;
    dateTo: string;
    simpleMap: string;
    a: string;
    b: string;
    c: number;
};

There are two minor issues: it doesn't exclude fields used in transformers, and the generic type E seems unnecessary (as I intended to omit fields but encountered limitations with TypeScript narrowing).

To address the first issue (and consequently the second), you can create a type like ActionConsumes to determine which fields are consumed, for example:

interface ActionConsumesFields {
    dateRange: 'from' | 'to';
    test: 'field';
}
type ActionConsumes<A extends Action<any>> = {
  [name in keyof ActionConsumesFields]:
    A extends { name: name } & { [key in ActionConsumesFields[name]]: any } ? A[ActionConsumesFields[name]] : never }[keyof ActionConsumesFields];

export default function useTransformer<IN, E extends keyof IN, M extends Record<keyof M, Action<E>>>(state: IN, mappings: M) {
  type N = keyof M;
  type Consumed = ActionConsumes<M[keyof M]>;
  return {
    form: {} as {
      [K in Exclude<E, Consumed> | N]: K extends N ? ActionResult<M[K]['name']> : (K extends keyof IN ? IN[K] : never);
    },
  };
}

// Updated form type
// (note the absence of dateFrom, dateTo, and a fields)
const form: {
    range: Date[];
    simpleMap: string;
    b: string;
    c: number;
};

(added the ActionConsumes concept)

I might find a way to trick TypeScript into automatically generating ActionConsumerFields after further investigation.


I've successfully implemented the feature to "auto-detect fields":

const _ACS = Symbol();
type ActionConsumesFields = {
  [action in Action<typeof _ACS> as action['name']]: keyof { [key in keyof action as action[key] extends typeof _ACS ? key : never]: 1; };
};

// Resulting ActionConsumesFields type:
type ActionConsumesFields = {
    dateRange: "from" | "to";
    test: "field";
};

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 handle success data in React Query?

Currently, I have an API call function (using httpClient as axios instance) interface IRegisterResponse { accessToken: string; } export const register = async ({ name, password, token, }: IRegisterParams) => await httpClient.post<IRegiste ...

Unable to locate identifiers 'Let' (TS2304), 'headers' (TS2552), and 'options' in a TypeScript script

I am new to using Angular and Ionic. While following a tutorial, I encountered the following errors: Cannot find name ‘Let’ (TS2304) Cannot find name ‘headers’. Did you mean ‘Headers’? (TS2552) Cannot find name ‘options’. Did you mean ‘ ...

Utilize useEffect to track a single property that relies on the values of several other properties

Below is a snippet of code: const MyComponent: React.FC<MyComponentProps> = ({ trackMyChanges, iChangeEverySecond }) => { // React Hook useEffect has missing dependencies: 'iChangeEverySecond' useEffect(() => { ...

Interactive MUI React Tab Components

Currently, I am working with MUI tabs and have added an X button to them. However, I am facing difficulties in making the tabs closeable. I would greatly appreciate any help or guidance on how to achieve this feature. Despite trying different methods, I ha ...

Tips for Dealing with Empty Rows in Arrays

How can I remove rows from an array in Alasql where all key values are null? Here is the array data: [ 0:{Name:"ABC1",No:5,BalanceDue:5000,Notes1:null,Notes2:null,CurrencyId:"2",Date:"06/01/2018"} 1:{Name:"ABC2",No:6,BalanceDue:6000,Notes1:null,Notes2: ...

Steps for updating a server component after redirectionWould you like to know how

One of my server components fetches and displays data only when the user is authorized: function CheckAuthorization() { const isAuthenticated = // check if user is authorized return ( <div> {isAuthenticated ? ( <DisplayAutho ...

Testing Angular components using mock HTML Document functionality is an important step in

Looking for help on testing a method in my component.ts. Here's the method: print(i) { (document.getElementById("iframe0) as any).contentWindow.print(); } I'm unsure how to mock an HTML document with an iframe in order to test this meth ...

Whenever I try to load the page and access the p-tableHeaderCheckbox in Angular (primeng), the checkbox appears to be disabled and I am unable to

I attempted to use the disabled attribute on p-tableheadercheckbox in order to activate the checkbox. <p-tableHeaderCheckbox [disabled]="false"></p-tableHeaderCheckbox> <ng-template pTemplate="header"> <tr> ...

Using TypeScript to specify data types in the Vue data object

I am currently utilizing Vue.js with Typescript in a webpack project. Following the guidelines provided in the Recommended Configuration in my tsconfig.json, I have set: "strict": true, Within one of my components, I have: declare interface P ...

Typescript double-sided dictionary: a comprehensive guide

Looking for a dual-sided dictionary implementation in TypeScript that allows you to retrieve values using keys and vice versa. An initial approach could be storing both items as keys: dict = {"key": "value", "value": "key"} But I am curious if there are ...

Is a shallow copy created by spreading?

According to the example provided in the documentation, let first:number[] = [1, 2]; let second:number[] = [3, 4]; let both_plus:number[] = [0, ...first, ...second, 5]; console.log(`both_plus is ${both_plus}`); first[0] = 20; console.log(`first is ${firs ...

Retrieve the response type from a Prisma FindUnique query

Essentially, my goal is to determine the type of the result obtained from a FindUnique operation in Prisma. The current return type is any: import prisma from "@/libs/prismaDb"; import { Prisma } from "@prisma/client"; export default a ...

What are some effective ways to identify all Typescript and ESLint errors across the entire NextJS project, rather than just the currently opened files

In my current NextJS project, I am searching for a way to display errors and warnings across the entire project, rather than just within the opened files. How can I achieve this? ...

Why do I keep getting an ExpressionChangedAfterItHasBeenChecked error after trying to update a random color in an

Is there a way to assign a random color from an array without causing the error message: "ExpressionChangedAfterItHasBeenChecked"? Even though the color of the chip changes quickly before the message appears, it seems like it's working. How can I reso ...

Is it feasible to evaluate a Typescript method parameter decorator at request time in a nodejs+nestjs environment rather than just at build time?

Looking to simplify my handling of mongodb calls with and without transactions in a single service method by writing a decorator. This would help eliminate the repetition of code and make things more efficient. Key points for usage: • Service class has ...

React Redux Saga doesn't trigger any actions

Currently, I am attempting to incorporate the following functionality: Users can successfully log in, but precisely after 5 seconds have passed, they are automatically logged out. My approach involves working with JSONWEBTOKEN. Here is my implementation u ...

How to format a Date object as yyyy-MM-dd when serializing to JSON in Angular?

I have a situation where I need to display an object's 'birthDate' property in the format DD/MM/YYYY on screen. The data is stored in the database as YYYY-MM-DD, which is a string type. I am currently using bootstrap bsDatePicker for this ta ...

Setting up Typescript: The Guide to Declaring a Dependent Property

In my current project, I am working on creating a declaration file for the quadstore library. This library has a class that requires a constructor parameter called contextKey. The value of this parameter determines the name of a field on method arguments. ...

What is the process for extracting the paths of component files from an Angular ngModule file?

I've been on the lookout for automation options to streamline the process of refactoring an Angular application, as doing it manually can be quite tedious. We're working on reducing our app's shared module by extracting components/directive ...

Apply a CSS class once a TypeScript function evaluates to true

Is it possible to automatically apply a specific CSS class to my Div based on the return value of a function? <div class="{{doubleClick === true ? 'cell-select' : 'cell-deselect'}}"></div> The doubleClick function will ret ...