Tips for defining a key: reducerFunctions object within a Typescript interface

Exploring the given interface:

interface TestState {
  a: number;
  b: string;
}

My goal is to create a generic type that enforces an object to:

  • have the same keys as a specified interface (e.g. TestState)
  • for each key, provide a value of a reducer function, with the state type in TestState.

For instance, an object following this pattern would appear like this:

const test: StateSlices<TestState> = {
  a: (state: number, action: any) => state,
  b: (state: string, action: any) => state,
};

I've attempted to define this type as shown below:

type StateSlices<T, K extends keyof T> = Record<
  keyof T,
  (state: T[K], action: any) => T[K]
>;

However, when I implement it like this:

const test: StateSlices<TestState, keyof TestState> = {
  a: (state: number, action: any) => state,
  b: (state: string, action: any) => state,
};

I encounter compilation errors related to typing conflicts between strings and numbers.

Type '(state: number, action: any) => number' is not assignable to type '(state: string | number, action: any) => string | number'.
  Types of parameters 'state' and 'state' are incompatible.

I'm struggling to find the correct typing for this scenario, despite my efforts throughout the day. Ideally, I want to specify types for actions too, but given the issues with state typing, I've kept them as 'any'.

Any assistance on this matter would be highly valued.

EDIT

In addition, if I wish to link various state slices with specific actions, what approach could be most suitable?

For example:

const test: StateSlices<TestState, keyof TestState> = {
  a: (state: number, action: "ADD" | "SUBTRACT") => state,
  b: (state: string, action: "TO_LOWERCASE" | "TO_UPPERCASE") => state,
};

How can I associate slice 'a' of TestState with its corresponding actions, preventing error matches like this:

const test: StateSlices<TestState, keyof TestState> = {
  a: (state: number, action: "TO_LOWERCASE" | "SUBTRACT") => state,
  b: (state: string, action: "SUBTRACT" | "TO_UPPERCASE") => state,
};

Your input is greatly appreciated.

Answer №1

Implement mapped types:

interface TestState {
  a: number;
  b: string;
}

type StateSlices<T> = {
    [K in keyof T]: (state: T[K], action: any) => T[K]
}

const test: StateSlices<TestState> = {
  a: (state, action) => state,
  b: (state, action) => state,
};

Try it out on TypeScript Playground

Appendix 1: In response to the second part of your question, you can utilize conditional types to execute specific actions based on the state type. While effective, this approach may not be suitable for all scenarios.

interface TestState {
  a: number;
  b: string;
}

type GetAction<T> =
  T extends number
    ? "ADD" | "SUBTRACT"
    : T extends string
      ? "TO_LOWERCASE" | "TO_UPPERCASE"
      : never;

type StateSlices<T> = {
    [K in keyof T]: (state: T[K], action: GetAction<T[K]>) => T[K]
}

const test: StateSlices<TestState> = {
  a: (state, action) => state,
  b: (state, action) => state,
};

Playground Link

Appendix 2 An alternate technique is demonstrated below:

interface TestState {
  a: number;
  b: string;
}

interface Actions {
  a: "ADD" | &...</exanswer1>
<div class="answer accepted" i="67706302" l="4.0" c="1622026118" m="1622041129" v="2" a="S2Fyb2wgTWFqZXdza2k=" ai="10325032">
<p>Use mapped types:</p>
<pre><code>interface TestState {
  a: number;
  b: string;
}

type StateSlices<T> = {
    [K in keyof T]: (state: T[K], action: any) => T[K]
}

const test: StateSlices<TestState> = {
  a: (state, action) => state,
  b: (state, action) => state,
};

Playground

Appendix 1: As for the second part of your question, you can use conditional types to obtain desired actions based on the type of your state. It will work, but it doesn't scale well.

interface TestState {
  a: number;
  b: string;
}

type GetAction<T> =
  T extends number
    ? "ADD" | "SUBTRACT"
    : T extends string
      ? "TO_LOWERCASE" | "TO_UPPERCASE"
      : never;

type StateSlices<T> = {
    [K in keyof T]: (state: T[K], action: GetAction<T[K]>) => T[K]
}

const test: StateSlices<TestState> = {
  a: (state, action) => state,
  b: (state, action) => state,
};

Playgorund

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

Steering clear of the generic Function type in React with TypeScript

Can anyone help me find a guideline that prohibits the use of "Function" as a type? myMethod: Function; I have searched but couldn't locate any information on this. Appreciate any suggestions :) ...

DateAdapter not found within Angular/Material/Datepicker - Provider not available

I need assistance with: angular / material / datepicker. My test project is running smoothly and consists of the following files: /src/app/app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from ' ...

Is it possible to assign default values to optional properties in JavaScript?

Here is an example to consider: interface Parameters { label: string; quantity?: number; } const defaultSettings = { label: 'Example', quantity: 10, }; function setup({ label, quantity }: Parameters = { ...defaultSettings }) { ...

How to Hide Warning Messages in Angular NPM Package for Production Environment

Seeking advice on a coding issue I'm facing. Currently, I am in the process of creating an npm package for angular / angular material, which involves implementing some checks. If a developer fails to pass a specific argument to my function, the funct ...

When importing a React Component with styling into the pages folder, it fails to function properly

I created a component in my components directory with custom styling: // import Link from "next/link"; import {Link} from "react-scroll" export default function Navbar() { return ( <div className="fixed w-full h-[79px] fle ...

"Ionic 3: Utilizing the If Statement within the subscribe() Function for Increased Results

I added an if conditional in my subscribe() function where I used return; to break if it meets the condition. However, instead of breaking the entire big function, it only breaks the subscribe() function and continues to execute the navCtrl.push line. How ...

Custom type declaration file in Typescript fails to function properly

I have searched through countless solutions to a similar issue, but none seem to work for me. I am attempting to utilize an npm package that lacks TypeScript type definitions, so I decided to create my own .d.ts file. However, every time I try, I encounter ...

Sharing information between components in Angular 4 and .NET Core applications

I am new to Angular and .NET Core. I have successfully created a web api using .NET Core, which is called from an Angular 4 application. Currently, everything is working smoothly. However, after submitting a form that inserts records into the database, I w ...

Is it possible to use an object's attribute as a switch case in TypeScript with useReducer?

I am attempting to convert switch case String into an object, but for some reason typescript is misunderstanding the switch case within useReducer: Prior to version update, everything was functioning correctly: export const LOGIN_USER = "LOGIN_USER&qu ...

In the domain of React and Typescript, a minimum of '3' arguments is anticipated; nonetheless, the JSX factory 'React.createElement' is only equipped with a maximum of '2' arguments. This incongruity is signaled by the

I am facing an error with this particular component: const TheBarTitle = ( theClass: any, columnTitle: string, onClickAction: any, ) => { return ( <div className={theClass} title="Click to add this ...

Tips on efficiently reusing shared components within recursive union types in TypeScript

Summary Here's a simple working example in the TypeScript playground: type SimpleExpression = number | string | AddOperator<SimpleExpression> | PrintOperator<SimpleExpression>; type ExtendedExpression = number | string | AddOperator<E ...

Angular not successfully passing ID in for loop

I am trying to pass the res[i].id value to my ArrayList while maintaining the sequence. Can anyone help me understand why 809 and 806 are not getting added to the arrayList correctly? 0: {id: 0, ArrayListID: 809, VarName: "TEST001A"} 1: {id: 0, ...

What could be causing Angular to replace the original variable?

As a newcomer to Angular, I've been working on this code for hours now. Hopefully, it will all come together for someone out there who can make sense of it. export class QuizComponent implements OnInit { originalArray: IArray[] = []; tempArray: I ...

The disabled attribute appears to be ineffective in an Angular reactive form

In my Angular reactive form, I have an email field that I want to disable when the form is in edit mode instead of add mode. The code I am using for this is: disabled: typeof user.user_id === 'string' When I debug the modelToForm method and che ...

What is the best method for embedding my token within my user entity?

Currently, I am working on implementing a "forgot password" feature in my application. The idea is that when a user requests to reset their password, they will receive a token via email that expires after two hours. To prevent the generation of multiple to ...

This error occurs because the argument type 'AsyncThunkAction<any, void, {}>' cannot be assigned to a parameter of type 'AnyAction'

I encountered an error that I couldn't find a solution for on Stack Overflow The argument of type 'AsyncThunkAction<any, void, {}>' is not compatible with the parameter of type 'AnyAction'. <MenuItem onClick={() =&g ...

Storing multiple items in an array using LocalForage

I have a challenge where I need to add multiple items to an array without overriding them. My initial approach was like this: localForage.getItem("data", (err, results) => { console.log('results', results) // var dataArray ...

Tips for mocking the router.navigate function in Jest

As a newcomer to unit testing with Jest in Angular, I find myself facing a challenge when it comes to testing components that utilize the this.router.navigate() method. Previously, I used Jasmine for testing and followed these steps: import { Router } from ...

Can you explain the functionality of `property IN array` in the TypeORM query builder?

I'm looking to filter a list of entity ids using query builder in an efficient way. Here's the code snippet I have: await this._productRepo .createQueryBuilder('Product') .where('Product.id IN (:...ids)', { ids: [1, 2, 3, 4] ...

Can wildcard paths be imported in React using Typescript?

Is there a way to dynamically import a React Typescript Component from a wildcard path, similar to the following code snippet? const Component = loadable( () => import(`../../../src/**/*/${component_name}`), ); I have searched numerous solutions on ...