Struggling with TypeScript generics and mapping record types is becoming quite a headache

What is the Purpose of my Code?

I have a set of functions stored in an object like this:

const actions = {
    foo: (state: string, action: {payload: string}) => {},
    bar: (state: string, action: {payload: number}) => {},
    baz: (state: string, action: {payload: boolean}) => {},
    qux: (state: string) => {}
}

Additionally, I have created a generic type as follows:

type ActionMapper<T> = T extends (state: any, action: infer Action) => any 
    ? Action extends {payload: infer P} 
        ? (payload: P) => void 
        : VoidFunction 
    : never

The current functionality of this type involves: https://i.sstatic.net/7RwuM.png

This setup allows for extracting the data type of the action.payload parameter from the actions.foo() function.

I now aim to create a generic type that performs a similar task but across the entire actions object.

Attempts Made So Far

I attempted to define a generic type with this structure:

type ActionsMapper<A> = Record<keyof A, ActionMapper<A[keyof A]>>

However, the outcome was not exactly what I had anticipated: https://i.sstatic.net/VM9mM.png https://i.sstatic.net/XQ7Tr.png

Instead of solely capturing the parameter type from actions.foo(), it collects it from all functions and generates a type union. My objective is for the type of mappedActions.foo() to be (payload: string) => void.

You can check out the TypeScript Playground I utilized here: TypeScript Playground.

Answer №1

When you encounter union types, it is likely due to the usage of

Record<keyof A, ActionMapper<A[keyof A]>>
. This creates an object with all keys from A, where each property has the type of ActionMapper applied to all values in A. However, what you actually need is an object with keys from A, where each property has the type of ActionMapper applied to the value of that same key in A.

To achieve this, you can utilize a mapped type:

type ActionsMapper<A> = {
  [K in keyof A]: ActionMapper<A[K]>
}

This mapped type produces the desired object, ensuring that the key used in the property's value matches the corresponding key.

TypeScript Playground


The crucial distinction lies in the fact that in a mapped type, each value corresponds to its respective key. In contrast, using Record results in uniform values for all properties.

For instance:

type ActionsMapper<A> = Record<keyof A, ActionMapper<A[keyof A]>>

Simplifying this yields:

type ARecord<T> = Record<keyof T, T[keyof T]>

In this case, keyof T comprises all the keys of T, while

T[keyof T]</code includes all the values of <code>T
. Notably, the two keyof expressions are independent.

We can similarly streamline the mapped type:

type AMapped<T> = {
  [K in keyof T]: T[K]
}

Comparing the Record-based type with the custom mapped type highlights the disparity clearly:

// Record<keyof T, T[keyof T]>
type RecordBased =
  { [K in keyof T]: T[keyof T] }

type Mapped =
  { [K in keyof T]: T[K]       }

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

Ways to trigger a function in Angular every 10 seconds

What is the method to utilize Observable function for fetching data from server every 10 seconds? Custom App service fetchDevices (): Observable<Device[]> { return this.http.get(this.deviceUrl) .map(this.extractData) .catch(this ...

Error message: TypeScript encountered an unexpected token '`div`' when it was expecting a jsx identifier

While working on a website using nextjs-typescript and tailwindcss, I encountered an unusual error message Expression expected. The terminal also displayed the following: Unexpected token `div`. Expected jsx identifier const UseCases = () => { 7 ...

Creating a dynamic selection in Angular without duplicate values

How can I prevent repetition of values when creating a dynamic select based on an object fetched from a database? Below is the HTML code: <router-outlet></router-outlet> <hr> <div class="row"> <div class="col-xs-12"> & ...

Guide to Generating a Compilation Error with Method Decorators in Typescript

Currently, I am developing a library named expresskit which allows the use of decorators to define routes, params, and other functionalities for express. While refactoring the codebase, I am considering implementing restrictions on the types of responses a ...

Is it possible for TypeScript to convert a generic enum type into a string at runtime?

Enumerations and interfaces are an important part of my codebase: enum EventId { FOO = 'FOO', BAR = 'BAR', } interface EventIdOptionsMap { [EventId.FOO]: { fooOption: string; }, [EventId.BAR]: { barOption: number; } ...

Issue: The "target" key is deprecated and will not be recognized in next.config.js anymore

WARNING - The next.config.js file contains invalid options: The root value has an unexpected property, target, which is not in the list of allowed properties (amp, analyticsId, assetPrefix, basePath, cleanDistDir, compiler, compress, crossOrigin, devInd ...

JavaScript Tutorial: Adding Custom Metadata to PDFs

Does anyone know of a JavaScript package that can assist in adding custom metadata to a PDF file? ...

I am having trouble with CSS, JS, and image files not loading when using Angular 9 loadChildren

I am facing an issue with loading CSS, JS, and images in my Angular 9 project. I have separate 'admin' and 'catalog' folders where I want to load different components. However, the assets are not loading properly in the 'catalog&ap ...

Using TypeScript 3.0 alongside React defaultProps

If I define a prop in the following way: interface Props { Layout?: LayoutComponent; } Then, if I set defaultProps on a ClassComponent: class MyComp extends React.Component<Props> { static defaultProps: Pick<Props, 'Layout'> = ...

Directive for masking input values

I am in need of an input that adheres to the following format: [00-23]:[00-59] Due to the limitations of Angular 2.4, where the pattern directive is unavailable and external libraries like primeNG cannot be used, I have been attempting to create a direct ...

Create a standalone 404 page using React Router that is completely isolated from any other components

Currently, I am collaborating with <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dfadbabebcabbaadf2adb0aaabbaadf2bbb0b29fe9f1ebf1ed">[email protected]</a> and the code I am working on looks like this: index.tsx ...

Issue with clicking the login button in Protractor with TypeScript

I have set up Page objects for the home page and login page. As I proceed with my test cases, I am encountering an issue with the login button. During the execution of my test cases, the login button is not responding to clicks. Has anyone else faced sim ...

Implementation of a function in Typescript that can be defined with a

I am currently diving into the Typescript specification and I'm facing a challenge in creating a functional implementation for describable functions. https://www.typescriptlang.org/docs/handbook/2/functions.html The provided example lacks completene ...

Advanced Typescript Interface Incorporating Objects

I'm facing an issue with my address interface setup. Here is how it's defined: export interface Address { addressType: { house?: { streetAddress: string, city: string, zip: string, }, ...

Issue with subscribing in a MEAN stack application

Currently, I have completed the backend development of my application and am now working on the frontend. My focus at the moment is on implementing the register component within my application. Below is the code snippet for my Register Component where I a ...

Incorporating type declarations for a basic function that returns a wrapper function for other functions

In my vanilla JS code, I have a sophisticated function that is exported and I am attempting to create a .d.ts file for it. Unfortunately, my previous attempts at writing the .d.ts file have not been successful in passing the types from one stage of the fu ...

Properly specifying the data type for a generic type variable within a function in TypeScript

As I work on my express project, I am currently coding a function called route. const morph = (params: Function[]) => (req: Request) => params.map(f => f(req)) const applyTransformers = (transformers: Function[]) => (response: any) => { ...

The pipe operator is not compatible with Angular (Typescript) when using the rxjs combineLatest function

Identifying the Issue in the Code import { Injectable } from '@angular/core'; import { AccountingTransactionsStoreService } from './accounting-transactions-store.service'; import { GeneralLedgerAccountsStoreService } from './gener ...

I want to know the most effective way to showcase particular information on a separate page using Angular

Recently, I've been working with a mock json file that contains a list of products to be displayed on a Product page. My goal is to select a specific product, such as 'Product 1', and have only that product's information displayed on th ...

Infinite Results Caused by Angular Nested *ngFor in an HTML Template

Currently, I am utilizing the Github API to create a Repositories Viewer. The approach involves taking repo.name as an input for another variable and passing it as a parameter to a function that fetches the languages used in a specific repository. However ...