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

Troubles with input handling in Angular

I've been diving into Traversy Media's Angular crash course recently. However, I've hit a roadblock that I just can't seem to get past. The problem arises when trying to style the button using a specific method. Every time I save and pa ...

Invoking a method in a derived class upon completion of asynchronous logic within the base class

Currently, I am in the process of building an Angular application. One aspect of my project involves a class that extends a base class. While this approach may not be ideal, I am curious to know what would be the best practice for BaseClass to trigger me ...

Angular/Typescript code not functioning properly due to faulty expressions

What could be causing my {{ expression }} to malfunction? I have exhausted all options, yet the web browser fails to recognize this {{ expression }} or properly bind it using ng-bind. Instead, it either displays the {{ expression }} as is or not at all. C ...

What is the process for transforming binary code into a downloadable file format?

Upon receiving a binary response from the backend containing the filename and its corresponding download type, the following code snippet illustrates the data: 01 00 00 00 78 02 00 00 6c 02 00 00 91 16 a2 3d ....x...l....... 9d e3 a6 4d 8a 4b b4 38 77 bc b ...

What is the best way to simulate an overloaded method in jest?

When working with the jsonwebtoken library to verify tokens in my module, I encountered a situation where the verify method is exported multiple times with different signatures. export function verify(token: string, secretOrPublicKey: Secret, options?: Ve ...

A script object must only permit one property at a time

I am unfamiliar with TypeScript and I have an object named obj with 3 properties: a, b, c. However, it is important to note that b and c cannot exist together in the same object. So, my object will either be: obj = { a: 'xxx', b: 'x ...

What is the best way to ensure that each service call to my controller is completed before proceeding to the next one within a loop in Angular?

Calling an Angular service can be done like this: this.webService.add(id) .subscribe(result => { // perform required actions }, error => { // handle errors }); // Service Definition add(id: number): Observable < any > { retu ...

The issue is that TypeScript is indicating that the type 'string | string[]' cannot be assigned to the type 'string'

I recently upgraded to Angular 16 and encountered an issue with an @Input() property of type string | string[]. Prior to the upgrade, everything was functioning correctly, but now I am experiencing errors. I am uncertain about where I may have gone wrong i ...

Executing a component method from a service class in Angular

When trying to call a component method from a service class, an error is encountered: 'ERROR TypeError: Cannot read property 'test' of undefined'. Although similar issues have been researched, the explanations mostly focus on component- ...

Migration of old AngularJS to TypeScript in require.js does not recognize import statements

I am looking to transition my aging AngularJS application from JavaScript to TypeScript. To load the necessary components, I am currently utilizing require.js. In order to maintain compatibility with scripts that do not use require.js, I have opted for usi ...

What is the process for converting/executing TypeScript into JavaScript?

Having trouble running https://github.com/airgram/airgram Encountering this warning message from the post (node:9374) Warning: To load an ES module, set "type": "module" Have already added {"type": "module"} To pa ...

When utilizing a custom hook that incorporates useContext, the updater function may fail to update as

After developing a customized react hook using useContext and useState, I encountered an issue where the state was not updating when calling the useState function within the consumer: import { createContext, ReactNode, useContext, useState, Dispatch, SetSt ...

Assigning a custom class to the cdk-overlay-pane within a mat-select component is restricted to Angular Material version 10.2.7

I attempted the code below, but it only works for angular material 11. My requirement is to use only angular material 10. providers: [ { provide: MAT_SELECT_CONFIG, useValue: { overlayPanelClass: 'customClass' } } ] There a ...

The type 'Requireable<string>' cannot be matched with the type 'Validator<"horizontal" | "vertical" | undefined>'

code import * as React from 'react'; import * as PropTypes from 'prop-types'; interface ILayoutProps { dir?: 'horizontal' | 'vertical' }; const Layout: React.FunctionComponent<ILayoutProps> = (props) => ...

How can I display a new module in Angular without navigating to it?

After following the tutorial at https://angular.io/guide/lazy-loading-ngmodules#create-a-feature-module-with-routing I set out to create the following: My goal is to have a dedicated module for all customer-related components accessible through the /cust ...

FilterService of PrimeNg

Looking for assistance with customizing a property of the p-columnFilter component. I have managed to modify the filter modes and customize the names, but I am having trouble with the no-filter option. Has anyone found a solution for this? this.matchMo ...

The TypeOrm many-to-one relationship with a multiple column join is giving an error: The column mentioned in the reference, <column name>, was not found in the entity <entity name>

I have an entity called A with a composite primary key, and another entity called B that has foreign keys referencing both columns of entity A. I am currently attempting to establish a many-to-one relationship between entity B (many) and entity A (one). U ...

Tips for Decreasing Query Time with MatTable and MatTableDataSource

When working with my firestore database, I am trying to query documents and display them while also calculating the total value of a specific column (promiAmount). I have successfully displayed the values in a mat table, but I'm struggling to calcula ...

Downloading fonts from Google Fonts is always a struggle when using Next.js

After initializing a fresh Next.js project using create-next-app, I managed to successfully launch it with npm run dev. However, an issue arises every time Next.js boots up, displaying the following error: FetchError: request to https://fonts.gstatic.com/ ...

The absence of a template or render function in a Vue.js 3 and Quasar 2 component has resulted in an

I am currently working on creating a dynamic component and passing a prop to it. However, I am encountering a warning message that says: Component is missing template or render function. Although the component is being rendered, I am still receiving the wa ...