Applying a generic function to every property of an object in TypeScript to deduce the return value

I'm facing an issue with TypeScript where I need to solve a problem:

type State = {
    alpha: string,
    beta: number
}

const selectors = {
    alpha: (s: State) => s.alpha,
    beta: (s: State) => s.beta
}

function createModifiedSelector<T, S, U>(
  modifyState: (differentState: T) => S,
  selector: (state: S) => U,
) {
  return (differentState: T) => selector(modifyState(differentState));
}

const add2State = (state: any) => {
    return {...state, alpha: "abc", beta: 123}
}

// everything is fine so far
const modifiedAlpha = createModifiedSelector(add2State, selectors.alpha)
console.log(modifiedAlpha({}));

// However, how can I convert the entire selectors object if there are multiple selectors?
function createModifiedSelectorsObj<T, S, R extends typeof selectors>(modifyState: (differentState: T) => S, selectors: R) {
    const result: Record<keyof R, any> = selectors;
    let selectorKey: keyof R;
    for (selectorKey in selectors) {
        result[selectorKey] = createModifiedSelector(modifyState, selectors[selectorKey])
    }
    return result;
}

Link to TS Playground.

In TypeScript, is there a way to provide correct typings for createModifiedSelectorsObj and the return values of the selector functions?

Answer №1

Here is a method you can use:

function generateModifiedSelectorsObject<T, S, U extends object>(
    modifyState: (customState: T) => S, selectors: { [K in keyof U]: (s: S) => U[K] }) {
    const result = {} as { [K in keyof U]: (t: T) => U[K] };
    let selectorKey: keyof U;
    (Object.keys(selectors) as Array<keyof U>).forEach(<K extends keyof U>(selectorKey: K) =>
        result[selectorKey] = generateModifiedSelector(modifyState, selectors[selectorKey])
    );
    return result;
}

The T and S type parameters stay the same as for a single selector function. Now, however, U is defined as an object type whose keys match those in the selectors parameter and values are the return types of the methods in the selectors object. If selectors has the type

{a: (s: State)=>string, b: (s: State)=>number}
, then U would be {a: string, b: number}.

This makes the type of selectors a mapped type over U. When calling

generateModifiedSelectorsObject()
, the compiler infers the type U from the expression
{[K in keyof U]: (u: S)=>U[K]}
, which simplifies the inference process.

In the function implementation, I specified the type of result to be another mapped type over U, where each method takes input of type T</code instead of <code>S. The initial assignment for result as an empty object

{}</code required a <a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions" rel="nofollow noreferrer">type assertion</a> to ensure compatibility with the expected type.</p>
<p>Instead of using a <code>for...in
loop, I used Object.keys().forEach() for iterating over the selectors because it allowed me to implement a generic callback where selectorKey could be of type K extends keyof U. Since Object.keys(obj) returns string[], I needed to assert the type to indicate that the keys in selectors correspond to keyof U.

The generic callback's implementation type-checks successfully since both sides of the assignment share the type (t: T) => U[K].


Let’s test this method:

const modifiedSelectorsObject = generateModifiedSelectorsObject(customizeState, selectors);
/* const modifiedSelectorsObject: {
    alpha: (t: any) => string;
    beta: (t: any) => number;
} */

The resulting modifiedSelectorsObject is an object containing methods with output matching those of the original selectors, but taking inputs that align with customizeState.

Link to Playground code

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

Tips for populating class attributes from an Angular model

Suppose there is a Class Vehicle with the following properties: public id: number; public modelId: number; public modelName: string; Now consider we have an object that looks like this {id: 1, modelId: 1, modelName: "4"} What is the best way to assign e ...

Tips for passing a usestate via props using interfaces in TypeScript and react?

I am currently working on implementing a light/dark theme and I have 2 components involved in the process. The first component code snippet is shown below, where I have successfully implemented a boolean to toggle between styles: export interface Props ...

What kind of conditions does SonarQube report as being uncovered?

According to SonarQube, there are uncovered conditions on all the arguments passed to constructors for each component in my Angular project, as well as any elements decorated with @Input(). What specific conditions is SonarQube referring to, and how can I ...

The 'BaseResponse<IavailableParameters[]>' type does not contain the properties 'length', 'pop', etc, which are expected to be present in the 'IavailableParameters[]' type

After making a get call to my API and receiving a list of objects, I save that data to a property in my DataService for use across components. Here is the code snippet from my component that calls the service: getAvailableParameters() { this.verifi ...

Typescript polymorphism allows for the ability to create various

Take a look at the following code snippet: class Salutation { message: string; constructor(text: string) { this.message = text; } greet() { return "Bonjour, " + this.message; } } class Greetings extends Salutation { ...

Can you effectively leverage a prop interface in React Typescript by combining it with another prop?

Essentially, I am looking to create a dynamic connection between the line injectComponentProps: object and the prop interface of the injectComponent. For example, it is currently set as injectComponentProps: InjectedComponentProps, but I want this associat ...

Include the request model as a parameter

I'm encountering an issue when trying to pass a model as a parameter to an API, resulting in the following error: Type 'string' is not assignable to type 'AVAFModel'. submitAvafDetails(): Observable<any> { this.avafBcmsV ...

How can I best declare a reactive variable without a value in Vue 3 using TypeScript?

Is there a way to initialize a reactive variable without assigning it a value initially? After trying various methods, I found that using null as the initial value doesn't seem to work: const workspaceReact = reactive(null) // incorrect! Cannot pass n ...

Enforcing strict type checking in TypeScript with noImplicitAny for undeclared variables

Why doesn't TypeScript give a warning about implicit any when creating a variable whose type changes through multiple assignments? // TypeScript allows this without any warnings let variable; variable = "string"; variable = 5; // But it won ...

Combining marker, circle, and polygon layers within an Angular 5 environment

I am working on a project where I have an array of places that are being displayed in both a table and a map. Each element in the table is represented by a marker, and either a circle or polygon. When an element is selected from the table, the marker icon ...

"Optimize Your Data with PrimeNG's Table Filtering Feature

I'm currently working on implementing a filter table using PrimeNG, but I'm facing an issue with the JSON structure I receive, which has multiple nested levels. Here's an example: { "id": "123", "category": "nice", "place": { "ran ...

React/Ionic: Avoiding SVG rendering using <img/> elements

I seem to be encountering an issue when trying to load SVG's in my React/Ionic App. I am fetching weather data from OpenWeatherMap and using the weather?.weather[0].icon property to determine which icon to display. I am utilizing icons from the follow ...

React: Function is missing a return type declaration. eslint plugin @typescript-eslint urges for explicit function return types

I'm just starting out with Typescript in React. I've created a simple functional component, but eslint is giving me an error saying that the return type for the functional component itself is missing. Can anyone help me figure out what I'm d ...

Can you provide an alternative to employing various restriction boundaries in a generic function in Kotlin other than casting?

I have a scenario where I need to work with three different data classes and I am looking to create a generic function to map them: data class Visits( val current: List<CurrentVisit> val previous: List<PreviousVisit> ) data class Curre ...

Enhancing TypeScript with Generic Functions

Due to limitations in TS syntax, I am unable to use the following: anObject['aKey'] = 'aValue'; To work around this issue, I have created the interfaces below and made all objects inherit from them: interface KeyIndexable { [key: str ...

Updating a specific section of the DOM while altering the URL in Angular 2

I am in the process of developing a web application that showcases news articles. My goal is to create a single-page app where users can view a list of article titles and then click on a title to read the full content without reloading the entire page. I ...

Vitest encountered an issue fetching a local file

I am currently working on testing the retrieval of a local file. Within my project, there exists a YAML configuration file. During production, the filename may be altered as it is received via a web socket. The code functions properly in production, but ...

How can you test the RTK Listener middleware?

Exciting news - I am experimenting with the new listener middleware from redux-toolkit in combination with React and Typescript! I have achieved some great results so far, but now I am eager to take it to the next level by implementing tests for my logic. ...

Proper method for inserting a value into a string array in a React application using TypeScript

How can I properly add elements to a string array in react? I encountered an error message: Type '(string | string[])[]' is not assignable to type 'string[]' You can view the code on this playground link : Here Could it be that I&apos ...

Deleting a key from a type in TypeScript using subtraction type

I am looking to create a type in TypeScript called ExcludeCart<T>, which essentially removes a specified key (in this case, cart) from the given type T. For example, if we have ExcludeCart<{foo: number, bar: string, cart: number}>, it should re ...