The Action-Reducer Mapping feature is encountering a type error when handling multiple types of actions

Earlier today, I posed a question about creating a mapping between redux action types and reducers to handle each type explicitly.

After receiving helpful guidance on how to create the mapping, I encountered an error when attempting to use it in creating a reducer.

Consider the following code snippet (it's lengthy, but intended as a minimal example):

export type IActionReducerMapping<S, A extends IReduxAction<string>> = {
    [K in A['type']]: IReducer<S, Extract<A, { type: K }>>
};

interface IUser {
    id: number;
    name: string;
    age: number;
}

interface IUserState {
    [id: number]: IUser;
}

interface IAddUserAction {
    type: 'ADD_USER';
    payload: IUser;
}

interface ISetUserNameAction {
    type: 'SET_USER_NAME';
    payload: {
        id: IUser['id'];
        name: IUser['name'];
    }
}

type UserAction = IAddUserAction | ISetUserNameAction;

const mapping: IActionReducerMapping<IUserState, UserAction> = {
    'ADD_USER': (state, action) => ({
        ...state,
        [action.payload.id]: action.payload,
    }),

    'SET_USER_NAME': (state, action) => ({
        ...state,
        [action.payload.id]: {
            ...state[action.payload.id],
            name: action.payload.name,
        }
    }),
};

const userReducer = (state: IUserState, action: UserAction) => mapping[action.type](state, action);

The error received is as follows:

Argument of type 'UserAction' is not assignable to parameter of type 'IAddUserAction & ISetUserNameAction'. Type 'IAddUserAction' is not assignable to type 'IAddUserAction & ISetUserNameAction'. Type 'IAddUserAction' is not assignable to type 'ISetUserNameAction'. Types of property 'type' are incompatible. Type '"ADD_USER"' is not assignable to type '"SET_USER_NAME"'.ts(2345)

I'm struggling to identify where the typing issue lies. Any insights on this would be greatly appreciated.

Playground link for reference.


Edit:

The following modified approach successfully resolves the issue:

const userReducer = (state: IUserState, action: ISetUserNameAction | IAddUserAction) =>  {
    switch (action.type) {
        case 'ADD_USER':
            return mapping[action.type](state, action);
        case 'SET_USER_NAME':
            return mapping[action.type](state, action);
    }
}

It appears that specifying the potential types upfront allows the compiler to understand the compatibility better. The automatic inference of this remains somewhat mysterious though.

Answer №1

To resolve this issue, I implemented a new function that transforms a mapping into a reducer:

export const createReducerFromMapping = <S, A extends IReduxAction<string>>(mapping: IActionReducerMapping<S, A>) =>
    (state: S, action: A): S =>
        mapping[action.type](state, action);

With this in place, the usage is simplified to:

const userReducer = createReducerFromMapping(mapping);

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

Mastering Typescript lookup types - effectively limit the properties included in a merge operation with the Partial type

Exploring lookup types, I'm interested in creating a safe-merge utility function that can update an entity of type T with a subset of keys from another object. The objective is to leverage the TypeScript compiler to catch any misspelled properties or ...

What is the best method for showcasing information within an Angular Material table?

When using Angular material to display a list, I am facing an issue where the content of the list is not being displayed but the header is. Component: export class CompaniesComponent implements OnInit { displayedColumns: string[] = ['id']; ...

The character 'T' cannot be assigned to the data type 'number'

When working with an optional type argument function RECT(T), I encountered a situation where I need to check if the argument is an instance of date. If it is, I convert it to a number; if not, I use the number directly. However, I keep getting an error ...

What is preventing me from utilizing a union type in conjunction with redux connect?

Below is a brief example of the code I am working on: import { connect } from "react-redux"; interface ErrorProps { error: true; description: string; } interface NoErrorProps { error: false; } type TestProps = ErrorProps | NoErrorProps; ...

In TypeScript, the error "Property does not exist on type 'any[]'" indicates that a specific property is not recognized on

Working on my project using Textscript in Next Js has been mostly smooth sailing, but I keep encountering warnings in my script that say 'Property does not exist on type any[ ]'. The red line under the name, image, and price properties is a sourc ...

Dealing with Overwhelmingly Large Angular 5 Components

I'm currently developing a project in Angular 5 and one of our component files is becoming quite large, reaching nearly a thousand lines and continuing to grow. This will eventually make it difficult to manage and understand. We are seeking advice on ...

axios.get consistently delivers a Promise of type <Pending>

I have been searching for a solution to my issue, but so far none of the suggestions have worked for me. Below is the code that I am struggling with: const Element = () => { async function getEndData() { const data = (await getEnd()) ...

What is the best way to kickstart a Reactive Angular 2 form by utilizing an Observable?

My current strategy involves storing the form values in my ngrx store so that users can easily navigate around the site and return to the form if needed. The concept is to have the form values repopulate from the store using an observable. This is how I a ...

The subscription for the second Observable in RxJS concatMap is triggered individually

I am currently developing an Angular 6 application. I want the app to display a loading animation whenever there is a change in the route or if there are any pending HTTP requests. To achieve this, I have set up two Observables as follows: For httpPendingR ...

Tips on how child component can detect when the object passed from parent component has been updated in Angular

In the child component, I am receiving an object from the parent component that looks like this: { attribute: 'aaaa', attribute2: [ { value }, { value }, { value }, ] } This object is passed to th ...

Is there a more efficient method for invoking `emit` in Vue's Composition API from an external file?

Is there a more efficient way to access the emit function in a separate logic file? This is my current approach that is functioning well: foo.js export default (emit) => { const foo = () => { emit('bar') }; return { foo }; } When ...

Tips for fixing: "Object may be null" error in Angular routing

Currently, I am working on the angular heroes tutorial provided in the angular documentation and encountering an error. An issue has been detected, which states that the object is possibly 'null'. getHero(): void { const id = +this.route.snaps ...

The upcoming developer manages to execute the program successfully, however, it continues to load indefinitely

Executing the command yarn dev consistently runs successfully in my VS Code terminal: $ yarn dev yarn run v1.22.19 warning ..\..\..\..\package.json: No license field $ next dev ready - started server on 0.0.0.0:3000, url: http://localho ...

Unable to modify the active property of the specified object as it is read-only

Presented here is the interface: export interface ProductCommand extends ProductDetailsCommand { } This is the ProductDetailsCommand interface: export interface ProductDetailsCommand { id: string; active: boolean; archive: boolean; title: ...

Learn the steps to establish a one-to-many relational record with the help of Node.js and Sequelize-Typescript

Currently, I am working on Nodejs with sequelize-typescript to develop a CRUD system for a one-to-many relationship. Unfortunately, I have encountered an issue with my code that I cannot seem to pinpoint. While I am able to retrieve records successfully us ...

Create a function that retrieves the value associated with a specific path within an object

I want to implement a basic utility function that can extract a specific path from an object like the following: interface Human { address: { city: { name: string; } } } const human: Human = { address: { city: { name: "Town"}}}; getIn< ...

React-router-dom TypeScript error when defining the type of the prop parameter in the loader

I'm having trouble with the params prop in the loader prop within the routes. I've defined the params in TypeScript, but I'm getting errors that I don't understand. Any help would be appreciated, thanks in advance. I tried to use the Cu ...

Export an array of objects using the ExcelService module

I am working with an array named listTutors that looks like this: listTutors = [{ countryId: 'tt', gender: 'both', levelId: 'tg', sessionType: 'inPerson', dashboardStatus: ['notPublished', 'p ...

Leveraging Angular2's observable stream in combination with *ngFor

Below is the code snippet I am working with: objs = [] getObjs() { let counter = 0 this.myService.getObjs() .map((obj) => { counter = counter > 5 ? 0 : counter; obj.col = counter; counter++; return view ...

Incorporating ngrx/Store into a current Angular application

Currently, I am working on an Angular 7 project that consists of numerous components communicating with an API to update data. The constant refreshing of the data using setTimeout has made it quite overwhelming as all the components are pulling data from t ...