reactjstypescriptreduxredux-logictypesafe-actions

How to make typesafe-actions work with redux-logic transform?


Redux-logic provides a method called transform, which allows actions to be transformed before they reach the reducers. This is helpful for various situations. Typically I want to dispatch an action which has only my user input, and sometimes my reducer will need supporting data in order to perform the state update.

So, I can do this in a redux-logic transform process, but this seems to conflict with typesafe-actions (which I'm also using). How can I tell typescript that my actions are getting transformed before they reach the reducer?

Imagine I dispatch with a payload like { userName: string } but my transform process adds a property so I get { userName: 'John Doe', recordId: 9 }.


Solution

  • I'm new to typescript, but realized that I know how to solve this.

    In the reducer we normally import the action types, it goes like this:

    // actions.ts
    import { action } from 'typesafe-actions';
    import actionTypes from './actionTypes';
    
    export const someAction = (data) = action(
      actionTypes.SOME_ACTION_TYPE,
      { data },
    );
    
    // reducer.ts
    import { ActionType } from 'typesafe-actions';
    import { StateSlice } from './types';
    import * as actions from './actions';
    
    const initialState: StateSlice = { ... };
    
    const reducer = (state = initialState, action: ActionType<typeof actions>): StateSlice => {
      ...
    };
    

    So to update this to accept actions which have been modified by a redux-logic middleware, we have to tell typescript that the type has been extended. It goes like this:

    // actions.ts
    import { action } from 'typesafe-actions';
    import actionTypes from './actionTypes';
    
    export const someAction = (data) = action(
      actionTypes.SOME_ACTION_TYPE,
      { data },
    );
    
    // reducer.ts
    import { ActionType } from 'typesafe-actions';
    import { StateSlice, SOME_TYPE } from './types';
    import * as actions from './actions';
    
    type OriginalAction = ActionType<typeof actions.someAction>;
    interface MutatedAction1 extends OriginalAction {
      payload: OriginalAction['payload'] & {
        thingWeAddedInReduxLogic: SOME_TYPE;
      };
    }
    
    // repeat for other actions, then use a union type like this
    
    type MutatedActionType = MutatedAction1 | MutatedAction2 | ... ;
    
    const initialState: StateSlice = { ... };
    
    const reducer = (state = initialState, action: MutatedActionType): StateSlice => {
      ...
    };
    

    This is an abbreviated version of the real code that I was able to make work. Hope this helped someone.