How to type Redux actions and Redux reducers in TypeScript?












21















What is the best way to cast the action parameter in a redux reducer with typescript? There will be multiple action interfaces that can occur that all extend a base interface with a property type. The extended action interfaces can have more properties that are all different between the action interfaces. Here is an example below:



interface IAction {
type: string
}

interface IActionA extends IAction {
a: string
}

interface IActionB extends IAction {
b: string
}

const reducer = (action: IAction) {
switch (action.type) {
case 'a':
return console.info('action a: ', action.a) // property 'a' does not exists on type IAction

case 'b':
return console.info('action b: ', action.b) // property 'b' does not exists on type IAction
}
}


The problem is that action needs to be cast as a type that has access to both IActionA and IActionB so the reducer can use both action.a and action.a without throwing an error.



I have several ideas how to work around this issue:




  1. Cast action to any.

  2. Use optional interface members.


example:



interface IAction {
type: string
a?: string
b?: string
}



  1. Use different reducers for every action type.


What is the best way to organize Action/Reducers in typescript? Thank you in advance!










share|improve this question

























  • check this ,spin.atomicobject.com/2017/07/24/…

    – Ahmad Dehnavi
    Dec 12 '17 at 8:41
















21















What is the best way to cast the action parameter in a redux reducer with typescript? There will be multiple action interfaces that can occur that all extend a base interface with a property type. The extended action interfaces can have more properties that are all different between the action interfaces. Here is an example below:



interface IAction {
type: string
}

interface IActionA extends IAction {
a: string
}

interface IActionB extends IAction {
b: string
}

const reducer = (action: IAction) {
switch (action.type) {
case 'a':
return console.info('action a: ', action.a) // property 'a' does not exists on type IAction

case 'b':
return console.info('action b: ', action.b) // property 'b' does not exists on type IAction
}
}


The problem is that action needs to be cast as a type that has access to both IActionA and IActionB so the reducer can use both action.a and action.a without throwing an error.



I have several ideas how to work around this issue:




  1. Cast action to any.

  2. Use optional interface members.


example:



interface IAction {
type: string
a?: string
b?: string
}



  1. Use different reducers for every action type.


What is the best way to organize Action/Reducers in typescript? Thank you in advance!










share|improve this question

























  • check this ,spin.atomicobject.com/2017/07/24/…

    – Ahmad Dehnavi
    Dec 12 '17 at 8:41














21












21








21


5






What is the best way to cast the action parameter in a redux reducer with typescript? There will be multiple action interfaces that can occur that all extend a base interface with a property type. The extended action interfaces can have more properties that are all different between the action interfaces. Here is an example below:



interface IAction {
type: string
}

interface IActionA extends IAction {
a: string
}

interface IActionB extends IAction {
b: string
}

const reducer = (action: IAction) {
switch (action.type) {
case 'a':
return console.info('action a: ', action.a) // property 'a' does not exists on type IAction

case 'b':
return console.info('action b: ', action.b) // property 'b' does not exists on type IAction
}
}


The problem is that action needs to be cast as a type that has access to both IActionA and IActionB so the reducer can use both action.a and action.a without throwing an error.



I have several ideas how to work around this issue:




  1. Cast action to any.

  2. Use optional interface members.


example:



interface IAction {
type: string
a?: string
b?: string
}



  1. Use different reducers for every action type.


What is the best way to organize Action/Reducers in typescript? Thank you in advance!










share|improve this question
















What is the best way to cast the action parameter in a redux reducer with typescript? There will be multiple action interfaces that can occur that all extend a base interface with a property type. The extended action interfaces can have more properties that are all different between the action interfaces. Here is an example below:



interface IAction {
type: string
}

interface IActionA extends IAction {
a: string
}

interface IActionB extends IAction {
b: string
}

const reducer = (action: IAction) {
switch (action.type) {
case 'a':
return console.info('action a: ', action.a) // property 'a' does not exists on type IAction

case 'b':
return console.info('action b: ', action.b) // property 'b' does not exists on type IAction
}
}


The problem is that action needs to be cast as a type that has access to both IActionA and IActionB so the reducer can use both action.a and action.a without throwing an error.



I have several ideas how to work around this issue:




  1. Cast action to any.

  2. Use optional interface members.


example:



interface IAction {
type: string
a?: string
b?: string
}



  1. Use different reducers for every action type.


What is the best way to organize Action/Reducers in typescript? Thank you in advance!







typescript redux






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 8 '18 at 15:57









jjbskir

2,84431731




2,84431731










asked Feb 18 '16 at 13:02









Roman KlimenkoRoman Klimenko

3301413




3301413













  • check this ,spin.atomicobject.com/2017/07/24/…

    – Ahmad Dehnavi
    Dec 12 '17 at 8:41



















  • check this ,spin.atomicobject.com/2017/07/24/…

    – Ahmad Dehnavi
    Dec 12 '17 at 8:41

















check this ,spin.atomicobject.com/2017/07/24/…

– Ahmad Dehnavi
Dec 12 '17 at 8:41





check this ,spin.atomicobject.com/2017/07/24/…

– Ahmad Dehnavi
Dec 12 '17 at 8:41












18 Answers
18






active

oldest

votes


















25














With Typescript 2's Tagged Union Types you can do the following



interface ActionA {
type: 'a';
a: string
}

interface ActionB {
type: 'b';
b: string
}

type Action = ActionA | ActionB;

function reducer(action:Action) {
switch (action.type) {
case 'a':
return console.info('action a: ', action.a)
case 'b':
return console.info('action b: ', action.b)
}
}





share|improve this answer































    11














    I have an Action interface



    export interface Action<T, P> {
    readonly type: T;
    readonly payload?: P;
    }


    I have a createAction function:



    export function createAction<T extends string, P>(type: T, payload: P): Action<T, P> {
    return { type, payload };
    }


    I have an action type constant:



    const IncreaseBusyCountActionType = "IncreaseBusyCount";


    And I have an interface for the action (check out the cool use of typeof):



    type IncreaseBusyCountAction = Action<typeof IncreaseBusyCountActionType, void>;


    I have an action creator function:



    function createIncreaseBusyCountAction(): IncreaseBusyCountAction {
    return createAction(IncreaseBusyCountActionType, null);
    }


    Now my reducer looks something like this:



    type Actions = IncreaseBusyCountAction | DecreaseBusyCountAction;

    function busyCount(state: number = 0, action: Actions) {
    switch (action.type) {
    case IncreaseBusyCountActionType: return reduceIncreaseBusyCountAction(state, action);
    case DecreaseBusyCountActionType: return reduceDecreaseBusyCountAction(state, action);
    default: return state;
    }
    }


    And I have a reducer function per action:



    function reduceIncreaseBusyCountAction(state: number, action: IncreaseBusyCountAction): number {
    return state + 1;
    }





    share|improve this answer
























    • Nice setup, I will follow your example. However for the action type constant I would rather use something like this: const AccountActions = {GET_CURRENT_ACCOUNT_ASYNC: "GET_CURRENT_ACCOUNT_ASYNC"}. As a matter of covenience I tend to group them in a single object. And for large apps I tend to group them by module and domain such as AccountDataActions & AccountUiActions. This way I have to do a lot less typing when importing actions. And for further convenience I keep action type constants and action creators in the same object.

      – Adrian Moisa
      Mar 19 '17 at 14:46











    • This is still a good approach, but I switched to another one, that I have also put here as an answer to this question!

      – Elmer
      Jan 6 '18 at 15:46



















    5














    For a relatively simple reducer you could probably just use type guards:



    function isA(action: IAction): action is IActionA {
    return action.type === 'a';
    }

    function isB(action: IAction): action is IActionB {
    return action.type === 'b';
    }

    function reducer(action: IAction) {
    if (isA(action)) {
    console.info('action a: ', action.a);
    } else if (isB(action)) {
    console.info('action b: ', action.b);
    }
    }





    share|improve this answer
























    • you had me at simple

      – Yehuda Makarov
      Mar 1 at 4:14



















    5














    Here's a clever solution from Github user aikoven from https://github.com/reactjs/redux/issues/992#issuecomment-191152574:



    type Action<TPayload> = {
    type: string;
    payload: TPayload;
    }

    interface IActionCreator<P> {
    type: string;
    (payload: P): Action<P>;
    }

    function actionCreator<P>(type: string): IActionCreator<P> {
    return Object.assign(
    (payload: P) => ({type, payload}),
    {type}
    );
    }

    function isType<P>(action: Action<any>,
    actionCreator: IActionCreator<P>): action is Action<P> {
    return action.type === actionCreator.type;
    }


    Use actionCreator<P> to define your actions and action creators:



    export const helloWorldAction = actionCreator<{foo: string}>('HELLO_WORLD');
    export const otherAction = actionCreator<{a: number, b: string}>('OTHER_ACTION');


    Use the user defined type guard isType<P> in the reducer:



    function helloReducer(state: string = ['hello'], action: Action<any>): string {
    if (isType(action, helloWorldAction)) { // type guard
    return [...state, action.payload.foo], // action.payload is now {foo: string}
    }
    else if(isType(action, otherAction)) {
    ...


    And to dispatch an action:



    dispatch(helloWorldAction({foo: 'world'})
    dispatch(otherAction({a: 42, b: 'moon'}))


    I recommend reading through the whole comment thread to find other options as there are several equally good solutions presented there.






    share|improve this answer































      5














      Here is how I do it:



      IAction.ts



      import {Action} from 'redux';

      /**
      * https://github.com/acdlite/flux-standard-action
      */
      export default interface IAction<T> extends Action<string> {
      type: string;
      payload?: T;
      error?: boolean;
      meta?: any;
      }


      UserAction.ts



      import IAction from '../IAction';
      import UserModel from './models/UserModel';

      export type UserActionUnion = void | UserModel;

      export default class UserAction {

      public static readonly LOAD_USER: string = 'UserAction.LOAD_USER';
      public static readonly LOAD_USER_SUCCESS: string = 'UserAction.LOAD_USER_SUCCESS';

      public static loadUser(): IAction<void> {
      return {
      type: UserAction.LOAD_USER,
      };
      }

      public static loadUserSuccess(model: UserModel): IAction<UserModel> {
      return {
      payload: model,
      type: UserAction.LOAD_USER_SUCCESS,
      };
      }

      }


      UserReducer.ts



      import UserAction, {UserActionUnion} from './UserAction';
      import IUserReducerState from './IUserReducerState';
      import IAction from '../IAction';
      import UserModel from './models/UserModel';

      export default class UserReducer {

      private static readonly _initialState: IUserReducerState = {
      currentUser: null,
      isLoadingUser: false,
      };

      public static reducer(state: IUserReducerState = UserReducer._initialState, action: IAction<UserActionUnion>): IUserReducerState {
      switch (action.type) {
      case UserAction.LOAD_USER:
      return {
      ...state,
      isLoadingUser: true,
      };
      case UserAction.LOAD_USER_SUCCESS:
      return {
      ...state,
      isLoadingUser: false,
      currentUser: action.payload as UserModel,
      };
      default:
      return state;
      }
      }

      }


      IUserReducerState.ts



      import UserModel from './models/UserModel';

      export default interface IUserReducerState {
      readonly currentUser: UserModel;
      readonly isLoadingUser: boolean;
      }


      UserSaga.ts



      import IAction from '../IAction';
      import UserService from './UserService';
      import UserAction from './UserAction';
      import {put} from 'redux-saga/effects';
      import UserModel from './models/UserModel';

      export default class UserSaga {

      public static* loadUser(action: IAction<void> = null) {
      const userModel: UserModel = yield UserService.loadUser();

      yield put(UserAction.loadUserSuccess(userModel));
      }

      }


      UserService.ts



      import HttpUtility from '../../utilities/HttpUtility';
      import {AxiosResponse} from 'axios';
      import UserModel from './models/UserModel';
      import RandomUserResponseModel from './models/RandomUserResponseModel';
      import environment from 'environment';

      export default class UserService {

      private static _http: HttpUtility = new HttpUtility();

      public static async loadUser(): Promise<UserModel> {
      const endpoint: string = `${environment.endpointUrl.randomuser}?inc=picture,name,email,phone,id,dob`;
      const response: AxiosResponse = await UserService._http.get(endpoint);
      const randomUser = new RandomUserResponseModel(response.data);

      return randomUser.results[0];
      }

      }


      https://github.com/codeBelt/typescript-hapi-react-hot-loader-example






      share|improve this answer

































        4














        Two parts of the problem



        Several comments above have mentioned concept/function `actionCreator´ -
        take a look at redux-actions package
        (and corresponding TypeScript definitions),
        that solves first part of the problem:
        creating action creator functions that have TypeScript type information specifying action payload type.



        Second part of the problem is combining reducer functions into single reducer without boilerplate code and in a type-safe manner
        (as the question was asked about TypeScript).



        The solution



        Combine
        redux-actions
        and redux-actions-ts-reducer packages:



        1) Create actionCreator functions that can be used for creating action with desired type and payload when dispatching the action:



        import { createAction } from 'redux-actions';

        const negate = createAction('NEGATE'); // action without payload
        const add = createAction<number>('ADD'); // action with payload type `number`


        2) Create reducer with initial state and reducer functions for all related actions:



        import { ReducerFactory } from 'redux-actions-ts-reducer';

        // type of the state - not strictly needed, you could inline it as object for initial state
        class SampleState {
        count = 0;
        }

        // creating reducer that combines several reducer functions
        const reducer = new ReducerFactory(new SampleState())
        // `state` argument and return type is inferred based on `new ReducerFactory(initialState)`.
        // Type of `action.payload` is inferred based on first argument (action creator)
        .addReducer(add, (state, action) => {
        return {
        ...state,
        count: state.count + action.payload,
        };
        })
        // no point to add `action` argument to reducer in this case, as `action.payload` type would be `void` (and effectively useless)
        .addReducer(negate, (state) => {
        return {
        ...state,
        count: state.count * -1,
        };
        })
        // chain as many reducer functions as you like with arbitrary payload types
        ...
        // Finally call this method, to create a reducer:
        .toReducer();


        As You can see from the comments You don't need to write any TypeScript type annotations, but all types are inferred
        (so this even works with noImplicitAny TypeScript compiler option)



        If You use actions from some framework that doesn't expose redux-action action creators (and You don't want to create them Yourself either)
        or have legacy code that uses strings constants for action types you could add reducers for them as well:



        const SOME_LIB_NO_ARGS_ACTION_TYPE = '@@some-lib/NO_ARGS_ACTION_TYPE';
        const SOME_LIB_STRING_ACTION_TYPE = '@@some-lib/STRING_ACTION_TYPE';

        const reducer = new ReducerFactory(new SampleState())
        ...
        // when adding reducer for action using string actionType
        // You should tell what is the action payload type using generic argument (if You plan to use `action.payload`)
        .addReducer<string>(SOME_LIB_STRING_ACTION_TYPE, (state, action) => {
        return {
        ...state,
        message: action.payload,
        };
        })
        // action.payload type is `void` by default when adding reducer function using `addReducer(actionType: string, reducerFunction)`
        .addReducer(SOME_LIB_NO_ARGS_ACTION_TYPE, (state) => {
        return new SampleState();
        })
        ...
        .toReducer();


        so it is easy to get started without refactoring Your codebase.



        Dispatching actions



        You can dispatch actions even without redux like this:



        const newState = reducer(previousState, add(5));


        but dispatching action with redux is simpler - use the dispatch(...) function as usual:



        dispatch(add(5));
        dispatch(negate());
        dispatch({ // dispatching action without actionCreator
        type: SOME_LIB_STRING_ACTION_TYPE,
        payload: newMessage,
        });



        Confession: I'm the author of redux-actions-ts-reducer that I open-sourced today.







        share|improve this answer


























        • Thank you very much for this solution! This helps me a lot for my current project. =)

          – fraherm
          Jun 1 '18 at 19:41





















        2














        you could do the following things



        if you expect one of IActionA or IActionB only, you can limit the type at least and define your function as



        const reducer = (action: (IActionA | IActionB)) => {
        ...
        }


        Now, the thing is, you still have to find out which type it is. You can totally add a type property but then, you have to set it somewhere, and interfaces are only overlays over object structures. You could create action classes and have the ctor set the type.



        Otherwise you have to verify the object by something else.
        In your case you could use hasOwnProperty and depending on that, cast it to the correct type:



        const reducer = (action: (IActionA | IActionB)) => {
        if(action.hasOwnProperty("a")){
        return (<IActionA>action).a;
        }

        return (<IActionB>action).b;
        }


        This would still work when compiled to JavaScript.






        share|improve this answer































          2














          The solution @Jussi_K referenced is nice because it's generic.



          However, I found a way that I like better, on five points:




          1. It has the action properties directly on the action object, rather than in a "payload" object -- which is shorter. (though if you prefer the "payload" prop, just uncomment the extra line in the constructor)

          2. It can be type-checked in reducers with a simple action.Is(Type), instead of the clunkier isType(action, createType).

          3. The logic's contained within a single class, instead of spread out amonst type Action<TPayload>, interface IActionCreator<P>, function actionCreator<P>(), function isType<P>().

          4. It uses simple, real classes instead of "action creators" and interfaces, which in my opinion is more readable and extensible. To create a new Action type, just do class MyAction extends Action<{myProp}> {}.

          5. It ensures consistency between the class-name and type property, by just calculating type to be the class/constructor name. This adheres to the DRY principle, unlike the other solution which has both a helloWorldAction function and a HELLO_WORLD "magic string".


          Anyway, to implement this alternate setup:



          First, copy this generic Action class:



          class Action<Payload> {
          constructor(payload: Payload) {
          this.type = this.constructor.name;
          //this.payload = payload;
          Object.assign(this, payload);
          }
          type: string;
          payload: Payload; // stub; needed for Is() method's type-inference to work, for some reason

          Is<Payload2>(actionType: new(..._)=>Action<Payload2>): this is Payload2 {
          return this.type == actionType.name;
          //return this instanceof actionType; // alternative
          }
          }


          Then create your derived Action classes:



          class IncreaseNumberAction extends Action<{amount: number}> {}
          class DecreaseNumberAction extends Action<{amount: number}> {}


          Then, to use in a reducer function:



          function reducer(state, action: Action<any>) {
          if (action.Is(IncreaseNumberAction))
          return {...state, number: state.number + action.amount};
          if (action.Is(DecreaseNumberAction))
          return {...state, number: state.number - action.amount};
          return state;
          }


          When you want to create and dispatch an action, just do:



          dispatch(new IncreaseNumberAction({amount: 10}));


          As with @Jussi_K's solution, each of these steps is type-safe.



          EDIT



          If you want the system to be compatible with anonymous action objects (eg, from legacy code, or deserialized state), you can instead use this static function in your reducers:



          function IsType<Payload>(action, actionType: new(..._)=>Action<Props>): action is Payload {
          return action.type == actionType.name;
          }


          And use it like so:



          function reducer(state, action: Action<any>) {
          if (IsType(action, IncreaseNumberAction))
          return {...state, number: state.number + action.amount};
          if (IsType(action, DecreaseNumberAction))
          return {...state, number: state.number - action.amount};
          return state;
          }


          The other option is to add the Action.Is() method onto the global Object.prototype using Object.defineProperty. This is what I'm currently doing -- though most people don't like this since it pollutes the prototype.



          EDIT 2



          Despite the fact that it would work anyway, Redux complains that "Actions must be plain objects. Use custom middleware for async actions.".



          To fix this, you can either:




          1. Remove the isPlainObject() checks in Redux.

          2. Do one of the modifications in my edit above, plus add this line to the end of the Action class's constructor: (it removes the runtime link between instance and class)


          Object.setPrototypeOf(this, Object.getPrototypeOf({}));





          share|improve this answer

































            2














            To get implicit typesafety without having to write interfaces for every action, you can use this approach (inspired by the returntypeof function from here: https://github.com/piotrwitek/react-redux-typescript#returntypeof-polyfill)



            import { values } from 'underscore'

            /**
            * action creator (declaring the return type is optional,
            * but you can make the props readonly)
            */
            export const createAction = <T extends string, P extends {}>(type: T, payload: P) => {
            return {
            type,
            payload
            } as {
            readonly type: T,
            readonly payload: P
            }
            }

            /**
            * Action types
            */
            const ACTION_A = "ACTION_A"
            const ACTION_B = "ACTION_B"

            /**
            * actions
            */
            const actions = {
            actionA: (count: number) => createAction(ACTION_A, { count }),
            actionB: (name: string) => createAction(ACTION_B, { name })
            }

            /**
            * create action type which you can use with a typeguard in the reducer
            * the actionlist variable is only needed for generation of TAction
            */
            const actionList = values(actions).map(returnTypeOf)
            type TAction = typeof actionList[number]

            /**
            * Reducer
            */
            export const reducer = (state: any, action: TAction) => {
            if ( action.type === ACTION_A ) {
            console.log(action.payload.count)
            }
            if ( action.type === ACTION_B ) {
            console.log(action.payload.name)
            console.log(action.payload.count) // compile error, because count does not exist on ACTION_B
            }
            console.log(action.payload.name) // compile error because name does not exist on every action
            }





            share|improve this answer































              2














              With Typescript v2, you can do this pretty easily using union types with type guards and Redux's own Action and Reducer types w/o needing to use additional 3rd party libs, and w/o enforcing a common shape to all actions (e.g. via payload).



              This way, your actions are correctly typed in your reducer catch clauses, as is the returned state.



              import {
              Action,
              Reducer,
              } from 'redux';

              interface IState {
              tinker: string
              toy: string
              }

              type IAction = ISetTinker
              | ISetToy;

              const SET_TINKER = 'SET_TINKER';
              const SET_TOY = 'SET_TOY';

              interface ISetTinker extends Action<typeof SET_TINKER> {
              tinkerValue: string
              }
              const setTinker = (tinkerValue: string): ISetTinker => ({
              type: SET_TINKER, tinkerValue,
              });
              interface ISetToy extends Action<typeof SET_TOY> {
              toyValue: string
              }
              const setToy = (toyValue: string): ISetToy => ({
              type: SET_TOY, toyValue,
              });

              const reducer: Reducer<IState, IAction> = (
              state = { tinker: 'abc', toy: 'xyz' },
              action
              ) => {
              // action is IAction
              if (action.type === SET_TINKER) {
              // action is ISetTinker
              // return { ...state, tinker: action.wrong } // doesn't typecheck
              // return { ...state, tinker: false } // doesn't typecheck
              return {
              ...state,
              tinker: action.tinkerValue,
              };
              } else if (action.type === SET_TOY) {
              return {
              ...state,
              toy: action.toyValue
              };
              }

              return state;
              }


              Things is basically what @Sven Efftinge suggests, while additionally checking the reducer's return type.






              share|improve this answer
























              • Thanks for updating @Sven Efftinge answer to use redux types!

                – jjbskir
                Nov 8 '18 at 15:31



















              2














              I am the author of ts-redux-actions-reducer-factory and would present you this as an another solution on top of the others.
              This package infers the action by action creator or by manually defined action type and - that's new - the state. So each reducer takes aware of the return type of previous reducers and represents therefore a possible extended state that must be initialized at the end, unless done at beginning. It is kind of special in its use, but can simplify typings.



              But here a complete possible solution on base of your problem:



              import { createAction } from "redux-actions";
              import { StateType } from "typesafe-actions";
              import { ReducerFactory } from "../../src";

              // Type constants
              const aType = "a";
              const bType = "b";

              // Container a
              interface IActionA {
              a: string;
              }

              // Container b
              interface IActionB {
              b: string;
              }

              // You define the action creators:
              // - you want to be able to reduce "a"
              const createAAction = createAction<IActionA, string>(aType, (a) => ({ a }));
              // - you also want to be able to reduce "b"
              const createBAction = createAction<IActionB, string>(aType, (b) => ({ b }));

              /*
              * Now comes a neat reducer factory into the game and we
              * keep a reference to the factory for example purposes
              */
              const factory = ReducerFactory
              .create()
              /*
              * We need to take care about other following reducers, so we normally want to include the state
              * by adding "...state", otherwise only property "a" would survive after reducing "a".
              */
              .addReducer(createAAction, (state, action) => ({
              ...state,
              ...action.payload!,
              }))
              /*
              * By implementation you are forced to initialize "a", because we
              * now know about the property "a" by previous defined reducer.
              */
              .addReducer(createBAction, (state, action) => ({
              ...state,
              ...action.payload!,
              }))
              /**
              * Now we have to call `acceptUnknownState` and are forced to initialize the reducer state.
              */
              .acceptUnknownState({
              a: "I am A by default!",
              b: "I am B by default!",
              });

              // At the very end, we want the reducer.
              const reducer = factory.toReducer();

              const initialState = factory.initialKnownState;
              // { a: "I am A by default!", b: "I am B by default!" }

              const resultFromA = reducer(initialState, createAAction("I am A!"));
              // { a: "I am A!", b: "I am B by default!" }

              const resultFromB = reducer(resultFromA, createBAction("I am B!"));
              // { a: "I am A!", b: "I am B!" }

              // And when you need the new derived type, you can get it with a module like @typesafe-actions
              type DerivedType = StateType<typeof reducer>;

              // Everything is type-safe. :)
              const derivedState: DerivedType = initialState;





              share|improve this answer

































                1














                you can define your action something like:



                // src/actions/index.tsx
                import * as constants from '../constants'

                export interface IncrementEnthusiasm {
                type: constants.INCREMENT_ENTHUSIASM;
                }

                export interface DecrementEnthusiasm {
                type: constants.DECREMENT_ENTHUSIASM;
                }

                export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;

                export function incrementEnthusiasm(): IncrementEnthusiasm {
                return {
                type: constants.INCREMENT_ENTHUSIASM
                }
                }

                export function decrementEnthusiasm(): DecrementEnthusiasm {
                return {
                type: constants.DECREMENT_ENTHUSIASM
                }
                }


                and so, you can define your reducer like follows:



                // src/reducers/index.tsx



                import { EnthusiasmAction } from '../actions';
                import { StoreState } from '../types/index';
                import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';

                export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
                switch (action.type) {
                case INCREMENT_ENTHUSIASM:
                return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
                case DECREMENT_ENTHUSIASM:
                return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
                }
                return state;
                }


                Complete official docs: https://github.com/Microsoft/TypeScript-React-Starter#adding-a-reducer






                share|improve this answer































                  1














                  If you need to fix your implementation exactly as you posted, this is the way how to fix it and get it working using type assertions , respectively as I show in the following:



                  interface IAction {
                  type: string
                  }

                  interface IActionA extends IAction {
                  a: string
                  }

                  interface IActionB extends IAction {
                  b: string
                  }

                  const reducer = (action: IAction) => {
                  switch (action.type) {
                  case 'a':
                  return console.info('action a: ', (<IActionA>action).a) // property 'a' exists because you're using type assertion <IActionA>

                  case 'b':
                  return console.info('action b: ', (<IActionB>action).b) // property 'b' exists because you're using type assertion <IActionB>
                  }
                  }


                  You can learn more on section "Type Guards and Differentiating Types"
                  of the official documentation: https://www.typescriptlang.org/docs/handbook/advanced-types.html






                  share|improve this answer

































                    1














                    To be fair there are many ways to type actions but I find this one very straight forward and has the less possible boilerplate as well (already discussed in this topic).



                    This approach tries to type the key called "payload" of actions.



                    Check this sample






                    share|improve this answer































                      1














                      Lately I have been using this approach:



                      export abstract class PlainAction {
                      public abstract readonly type: any;
                      constructor() {
                      return Object.assign({}, this);
                      }
                      }

                      export abstract class ActionWithPayload<P extends object = any> extends PlainAction {
                      constructor(public readonly payload: P) {
                      super();
                      }
                      }

                      export class BeginBusyAction extends PlainAction {
                      public readonly type = "BeginBusy";
                      }

                      export interface SendChannelMessageActionPayload {
                      message: string;
                      }

                      export class SendChannelMessageAction
                      extends ActionWithPayload<SendChannelMessageActionPayload>
                      {
                      public readonly type = "SendChannelMessage";
                      constructor(
                      message: string,
                      ) {
                      super({
                      message,
                      });
                      }
                      }


                      This here:



                      constructor() {
                      return Object.assign({}, this);
                      }


                      ensures that the Actions are all plain objects. Now you can make actions like this: const action = new BeginBusyAction(). (yay o/)






                      share|improve this answer































                        1














                        There are libraries that bundle most of the code mentioned in other answers: aikoven/typescript-fsa and dphilipson/typescript-fsa-reducers.



                        With these libraries all your actions and reducers code is statically typed and readable:



                        import actionCreatorFactory from "typescript-fsa";
                        const actionCreator = actionCreatorFactory();

                        interface State {
                        name: string;
                        balance: number;
                        isFrozen: boolean;
                        }

                        const INITIAL_STATE: State = {
                        name: "Untitled",
                        balance: 0,
                        isFrozen: false,
                        };

                        const setName = actionCreator<string>("SET_NAME");
                        const addBalance = actionCreator<number>("ADD_BALANCE");
                        const setIsFrozen = actionCreator<boolean>("SET_IS_FROZEN");

                        ...

                        import { reducerWithInitialState } from "typescript-fsa-reducers";

                        const reducer = reducerWithInitialState(INITIAL_STATE)
                        .case(setName, (state, name) => ({ ...state, name }))
                        .case(addBalance, (state, amount) => ({
                        ...state,
                        balance: state.balance + amount,
                        }))
                        .case(setIsFrozen, (state, isFrozen) => ({ ...state, isFrozen }));





                        share|improve this answer

































                          1














                          Here is how can you do it with redux-fluent:



                          enter image description hereenter image description here






                          share|improve this answer

































                            -2














                            Here's the approach I've taken for this problem:



                            const reducer = (action: IAction) {

                            const actionA: IActionA = action as IActionA;
                            const actionB: IActionB = action as IActionB;

                            switch (action.type) {
                            case 'a':
                            // Only ever use actionA in this context
                            return console.info('action a: ', actionA.a)

                            case 'b':
                            // Only ever use actionB in this context
                            return console.info('action b: ', actionB.b)
                            }
                            }


                            I'll be the first to admit there's a certain ugliness and hackiness to this approach, but I've actually found it to work pretty well in practice. In particular, I find that it makes the code easy to read and maintain because the action's intent is in the name and that also makes it easy to search for.






                            share|improve this answer























                              Your Answer






                              StackExchange.ifUsing("editor", function () {
                              StackExchange.using("externalEditor", function () {
                              StackExchange.using("snippets", function () {
                              StackExchange.snippets.init();
                              });
                              });
                              }, "code-snippets");

                              StackExchange.ready(function() {
                              var channelOptions = {
                              tags: "".split(" "),
                              id: "1"
                              };
                              initTagRenderer("".split(" "), "".split(" "), channelOptions);

                              StackExchange.using("externalEditor", function() {
                              // Have to fire editor after snippets, if snippets enabled
                              if (StackExchange.settings.snippets.snippetsEnabled) {
                              StackExchange.using("snippets", function() {
                              createEditor();
                              });
                              }
                              else {
                              createEditor();
                              }
                              });

                              function createEditor() {
                              StackExchange.prepareEditor({
                              heartbeatType: 'answer',
                              autoActivateHeartbeat: false,
                              convertImagesToLinks: true,
                              noModals: true,
                              showLowRepImageUploadWarning: true,
                              reputationToPostImages: 10,
                              bindNavPrevention: true,
                              postfix: "",
                              imageUploader: {
                              brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
                              contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
                              allowUrls: true
                              },
                              onDemand: true,
                              discardSelector: ".discard-answer"
                              ,immediatelyShowMarkdownHelp:true
                              });


                              }
                              });














                              draft saved

                              draft discarded


















                              StackExchange.ready(
                              function () {
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f35482241%2fhow-to-type-redux-actions-and-redux-reducers-in-typescript%23new-answer', 'question_page');
                              }
                              );

                              Post as a guest















                              Required, but never shown

























                              18 Answers
                              18






                              active

                              oldest

                              votes








                              18 Answers
                              18






                              active

                              oldest

                              votes









                              active

                              oldest

                              votes






                              active

                              oldest

                              votes









                              25














                              With Typescript 2's Tagged Union Types you can do the following



                              interface ActionA {
                              type: 'a';
                              a: string
                              }

                              interface ActionB {
                              type: 'b';
                              b: string
                              }

                              type Action = ActionA | ActionB;

                              function reducer(action:Action) {
                              switch (action.type) {
                              case 'a':
                              return console.info('action a: ', action.a)
                              case 'b':
                              return console.info('action b: ', action.b)
                              }
                              }





                              share|improve this answer




























                                25














                                With Typescript 2's Tagged Union Types you can do the following



                                interface ActionA {
                                type: 'a';
                                a: string
                                }

                                interface ActionB {
                                type: 'b';
                                b: string
                                }

                                type Action = ActionA | ActionB;

                                function reducer(action:Action) {
                                switch (action.type) {
                                case 'a':
                                return console.info('action a: ', action.a)
                                case 'b':
                                return console.info('action b: ', action.b)
                                }
                                }





                                share|improve this answer


























                                  25












                                  25








                                  25







                                  With Typescript 2's Tagged Union Types you can do the following



                                  interface ActionA {
                                  type: 'a';
                                  a: string
                                  }

                                  interface ActionB {
                                  type: 'b';
                                  b: string
                                  }

                                  type Action = ActionA | ActionB;

                                  function reducer(action:Action) {
                                  switch (action.type) {
                                  case 'a':
                                  return console.info('action a: ', action.a)
                                  case 'b':
                                  return console.info('action b: ', action.b)
                                  }
                                  }





                                  share|improve this answer













                                  With Typescript 2's Tagged Union Types you can do the following



                                  interface ActionA {
                                  type: 'a';
                                  a: string
                                  }

                                  interface ActionB {
                                  type: 'b';
                                  b: string
                                  }

                                  type Action = ActionA | ActionB;

                                  function reducer(action:Action) {
                                  switch (action.type) {
                                  case 'a':
                                  return console.info('action a: ', action.a)
                                  case 'b':
                                  return console.info('action b: ', action.b)
                                  }
                                  }






                                  share|improve this answer












                                  share|improve this answer



                                  share|improve this answer










                                  answered Nov 23 '16 at 7:41









                                  Sven EfftingeSven Efftinge

                                  2,3641212




                                  2,3641212

























                                      11














                                      I have an Action interface



                                      export interface Action<T, P> {
                                      readonly type: T;
                                      readonly payload?: P;
                                      }


                                      I have a createAction function:



                                      export function createAction<T extends string, P>(type: T, payload: P): Action<T, P> {
                                      return { type, payload };
                                      }


                                      I have an action type constant:



                                      const IncreaseBusyCountActionType = "IncreaseBusyCount";


                                      And I have an interface for the action (check out the cool use of typeof):



                                      type IncreaseBusyCountAction = Action<typeof IncreaseBusyCountActionType, void>;


                                      I have an action creator function:



                                      function createIncreaseBusyCountAction(): IncreaseBusyCountAction {
                                      return createAction(IncreaseBusyCountActionType, null);
                                      }


                                      Now my reducer looks something like this:



                                      type Actions = IncreaseBusyCountAction | DecreaseBusyCountAction;

                                      function busyCount(state: number = 0, action: Actions) {
                                      switch (action.type) {
                                      case IncreaseBusyCountActionType: return reduceIncreaseBusyCountAction(state, action);
                                      case DecreaseBusyCountActionType: return reduceDecreaseBusyCountAction(state, action);
                                      default: return state;
                                      }
                                      }


                                      And I have a reducer function per action:



                                      function reduceIncreaseBusyCountAction(state: number, action: IncreaseBusyCountAction): number {
                                      return state + 1;
                                      }





                                      share|improve this answer
























                                      • Nice setup, I will follow your example. However for the action type constant I would rather use something like this: const AccountActions = {GET_CURRENT_ACCOUNT_ASYNC: "GET_CURRENT_ACCOUNT_ASYNC"}. As a matter of covenience I tend to group them in a single object. And for large apps I tend to group them by module and domain such as AccountDataActions & AccountUiActions. This way I have to do a lot less typing when importing actions. And for further convenience I keep action type constants and action creators in the same object.

                                        – Adrian Moisa
                                        Mar 19 '17 at 14:46











                                      • This is still a good approach, but I switched to another one, that I have also put here as an answer to this question!

                                        – Elmer
                                        Jan 6 '18 at 15:46
















                                      11














                                      I have an Action interface



                                      export interface Action<T, P> {
                                      readonly type: T;
                                      readonly payload?: P;
                                      }


                                      I have a createAction function:



                                      export function createAction<T extends string, P>(type: T, payload: P): Action<T, P> {
                                      return { type, payload };
                                      }


                                      I have an action type constant:



                                      const IncreaseBusyCountActionType = "IncreaseBusyCount";


                                      And I have an interface for the action (check out the cool use of typeof):



                                      type IncreaseBusyCountAction = Action<typeof IncreaseBusyCountActionType, void>;


                                      I have an action creator function:



                                      function createIncreaseBusyCountAction(): IncreaseBusyCountAction {
                                      return createAction(IncreaseBusyCountActionType, null);
                                      }


                                      Now my reducer looks something like this:



                                      type Actions = IncreaseBusyCountAction | DecreaseBusyCountAction;

                                      function busyCount(state: number = 0, action: Actions) {
                                      switch (action.type) {
                                      case IncreaseBusyCountActionType: return reduceIncreaseBusyCountAction(state, action);
                                      case DecreaseBusyCountActionType: return reduceDecreaseBusyCountAction(state, action);
                                      default: return state;
                                      }
                                      }


                                      And I have a reducer function per action:



                                      function reduceIncreaseBusyCountAction(state: number, action: IncreaseBusyCountAction): number {
                                      return state + 1;
                                      }





                                      share|improve this answer
























                                      • Nice setup, I will follow your example. However for the action type constant I would rather use something like this: const AccountActions = {GET_CURRENT_ACCOUNT_ASYNC: "GET_CURRENT_ACCOUNT_ASYNC"}. As a matter of covenience I tend to group them in a single object. And for large apps I tend to group them by module and domain such as AccountDataActions & AccountUiActions. This way I have to do a lot less typing when importing actions. And for further convenience I keep action type constants and action creators in the same object.

                                        – Adrian Moisa
                                        Mar 19 '17 at 14:46











                                      • This is still a good approach, but I switched to another one, that I have also put here as an answer to this question!

                                        – Elmer
                                        Jan 6 '18 at 15:46














                                      11












                                      11








                                      11







                                      I have an Action interface



                                      export interface Action<T, P> {
                                      readonly type: T;
                                      readonly payload?: P;
                                      }


                                      I have a createAction function:



                                      export function createAction<T extends string, P>(type: T, payload: P): Action<T, P> {
                                      return { type, payload };
                                      }


                                      I have an action type constant:



                                      const IncreaseBusyCountActionType = "IncreaseBusyCount";


                                      And I have an interface for the action (check out the cool use of typeof):



                                      type IncreaseBusyCountAction = Action<typeof IncreaseBusyCountActionType, void>;


                                      I have an action creator function:



                                      function createIncreaseBusyCountAction(): IncreaseBusyCountAction {
                                      return createAction(IncreaseBusyCountActionType, null);
                                      }


                                      Now my reducer looks something like this:



                                      type Actions = IncreaseBusyCountAction | DecreaseBusyCountAction;

                                      function busyCount(state: number = 0, action: Actions) {
                                      switch (action.type) {
                                      case IncreaseBusyCountActionType: return reduceIncreaseBusyCountAction(state, action);
                                      case DecreaseBusyCountActionType: return reduceDecreaseBusyCountAction(state, action);
                                      default: return state;
                                      }
                                      }


                                      And I have a reducer function per action:



                                      function reduceIncreaseBusyCountAction(state: number, action: IncreaseBusyCountAction): number {
                                      return state + 1;
                                      }





                                      share|improve this answer













                                      I have an Action interface



                                      export interface Action<T, P> {
                                      readonly type: T;
                                      readonly payload?: P;
                                      }


                                      I have a createAction function:



                                      export function createAction<T extends string, P>(type: T, payload: P): Action<T, P> {
                                      return { type, payload };
                                      }


                                      I have an action type constant:



                                      const IncreaseBusyCountActionType = "IncreaseBusyCount";


                                      And I have an interface for the action (check out the cool use of typeof):



                                      type IncreaseBusyCountAction = Action<typeof IncreaseBusyCountActionType, void>;


                                      I have an action creator function:



                                      function createIncreaseBusyCountAction(): IncreaseBusyCountAction {
                                      return createAction(IncreaseBusyCountActionType, null);
                                      }


                                      Now my reducer looks something like this:



                                      type Actions = IncreaseBusyCountAction | DecreaseBusyCountAction;

                                      function busyCount(state: number = 0, action: Actions) {
                                      switch (action.type) {
                                      case IncreaseBusyCountActionType: return reduceIncreaseBusyCountAction(state, action);
                                      case DecreaseBusyCountActionType: return reduceDecreaseBusyCountAction(state, action);
                                      default: return state;
                                      }
                                      }


                                      And I have a reducer function per action:



                                      function reduceIncreaseBusyCountAction(state: number, action: IncreaseBusyCountAction): number {
                                      return state + 1;
                                      }






                                      share|improve this answer












                                      share|improve this answer



                                      share|improve this answer










                                      answered Jan 3 '17 at 11:31









                                      ElmerElmer

                                      6,55413434




                                      6,55413434













                                      • Nice setup, I will follow your example. However for the action type constant I would rather use something like this: const AccountActions = {GET_CURRENT_ACCOUNT_ASYNC: "GET_CURRENT_ACCOUNT_ASYNC"}. As a matter of covenience I tend to group them in a single object. And for large apps I tend to group them by module and domain such as AccountDataActions & AccountUiActions. This way I have to do a lot less typing when importing actions. And for further convenience I keep action type constants and action creators in the same object.

                                        – Adrian Moisa
                                        Mar 19 '17 at 14:46











                                      • This is still a good approach, but I switched to another one, that I have also put here as an answer to this question!

                                        – Elmer
                                        Jan 6 '18 at 15:46



















                                      • Nice setup, I will follow your example. However for the action type constant I would rather use something like this: const AccountActions = {GET_CURRENT_ACCOUNT_ASYNC: "GET_CURRENT_ACCOUNT_ASYNC"}. As a matter of covenience I tend to group them in a single object. And for large apps I tend to group them by module and domain such as AccountDataActions & AccountUiActions. This way I have to do a lot less typing when importing actions. And for further convenience I keep action type constants and action creators in the same object.

                                        – Adrian Moisa
                                        Mar 19 '17 at 14:46











                                      • This is still a good approach, but I switched to another one, that I have also put here as an answer to this question!

                                        – Elmer
                                        Jan 6 '18 at 15:46

















                                      Nice setup, I will follow your example. However for the action type constant I would rather use something like this: const AccountActions = {GET_CURRENT_ACCOUNT_ASYNC: "GET_CURRENT_ACCOUNT_ASYNC"}. As a matter of covenience I tend to group them in a single object. And for large apps I tend to group them by module and domain such as AccountDataActions & AccountUiActions. This way I have to do a lot less typing when importing actions. And for further convenience I keep action type constants and action creators in the same object.

                                      – Adrian Moisa
                                      Mar 19 '17 at 14:46





                                      Nice setup, I will follow your example. However for the action type constant I would rather use something like this: const AccountActions = {GET_CURRENT_ACCOUNT_ASYNC: "GET_CURRENT_ACCOUNT_ASYNC"}. As a matter of covenience I tend to group them in a single object. And for large apps I tend to group them by module and domain such as AccountDataActions & AccountUiActions. This way I have to do a lot less typing when importing actions. And for further convenience I keep action type constants and action creators in the same object.

                                      – Adrian Moisa
                                      Mar 19 '17 at 14:46













                                      This is still a good approach, but I switched to another one, that I have also put here as an answer to this question!

                                      – Elmer
                                      Jan 6 '18 at 15:46





                                      This is still a good approach, but I switched to another one, that I have also put here as an answer to this question!

                                      – Elmer
                                      Jan 6 '18 at 15:46











                                      5














                                      For a relatively simple reducer you could probably just use type guards:



                                      function isA(action: IAction): action is IActionA {
                                      return action.type === 'a';
                                      }

                                      function isB(action: IAction): action is IActionB {
                                      return action.type === 'b';
                                      }

                                      function reducer(action: IAction) {
                                      if (isA(action)) {
                                      console.info('action a: ', action.a);
                                      } else if (isB(action)) {
                                      console.info('action b: ', action.b);
                                      }
                                      }





                                      share|improve this answer
























                                      • you had me at simple

                                        – Yehuda Makarov
                                        Mar 1 at 4:14
















                                      5














                                      For a relatively simple reducer you could probably just use type guards:



                                      function isA(action: IAction): action is IActionA {
                                      return action.type === 'a';
                                      }

                                      function isB(action: IAction): action is IActionB {
                                      return action.type === 'b';
                                      }

                                      function reducer(action: IAction) {
                                      if (isA(action)) {
                                      console.info('action a: ', action.a);
                                      } else if (isB(action)) {
                                      console.info('action b: ', action.b);
                                      }
                                      }





                                      share|improve this answer
























                                      • you had me at simple

                                        – Yehuda Makarov
                                        Mar 1 at 4:14














                                      5












                                      5








                                      5







                                      For a relatively simple reducer you could probably just use type guards:



                                      function isA(action: IAction): action is IActionA {
                                      return action.type === 'a';
                                      }

                                      function isB(action: IAction): action is IActionB {
                                      return action.type === 'b';
                                      }

                                      function reducer(action: IAction) {
                                      if (isA(action)) {
                                      console.info('action a: ', action.a);
                                      } else if (isB(action)) {
                                      console.info('action b: ', action.b);
                                      }
                                      }





                                      share|improve this answer













                                      For a relatively simple reducer you could probably just use type guards:



                                      function isA(action: IAction): action is IActionA {
                                      return action.type === 'a';
                                      }

                                      function isB(action: IAction): action is IActionB {
                                      return action.type === 'b';
                                      }

                                      function reducer(action: IAction) {
                                      if (isA(action)) {
                                      console.info('action a: ', action.a);
                                      } else if (isB(action)) {
                                      console.info('action b: ', action.b);
                                      }
                                      }






                                      share|improve this answer












                                      share|improve this answer



                                      share|improve this answer










                                      answered Feb 18 '16 at 15:27









                                      Vadim MacagonVadim Macagon

                                      9,02113040




                                      9,02113040













                                      • you had me at simple

                                        – Yehuda Makarov
                                        Mar 1 at 4:14



















                                      • you had me at simple

                                        – Yehuda Makarov
                                        Mar 1 at 4:14

















                                      you had me at simple

                                      – Yehuda Makarov
                                      Mar 1 at 4:14





                                      you had me at simple

                                      – Yehuda Makarov
                                      Mar 1 at 4:14











                                      5














                                      Here's a clever solution from Github user aikoven from https://github.com/reactjs/redux/issues/992#issuecomment-191152574:



                                      type Action<TPayload> = {
                                      type: string;
                                      payload: TPayload;
                                      }

                                      interface IActionCreator<P> {
                                      type: string;
                                      (payload: P): Action<P>;
                                      }

                                      function actionCreator<P>(type: string): IActionCreator<P> {
                                      return Object.assign(
                                      (payload: P) => ({type, payload}),
                                      {type}
                                      );
                                      }

                                      function isType<P>(action: Action<any>,
                                      actionCreator: IActionCreator<P>): action is Action<P> {
                                      return action.type === actionCreator.type;
                                      }


                                      Use actionCreator<P> to define your actions and action creators:



                                      export const helloWorldAction = actionCreator<{foo: string}>('HELLO_WORLD');
                                      export const otherAction = actionCreator<{a: number, b: string}>('OTHER_ACTION');


                                      Use the user defined type guard isType<P> in the reducer:



                                      function helloReducer(state: string = ['hello'], action: Action<any>): string {
                                      if (isType(action, helloWorldAction)) { // type guard
                                      return [...state, action.payload.foo], // action.payload is now {foo: string}
                                      }
                                      else if(isType(action, otherAction)) {
                                      ...


                                      And to dispatch an action:



                                      dispatch(helloWorldAction({foo: 'world'})
                                      dispatch(otherAction({a: 42, b: 'moon'}))


                                      I recommend reading through the whole comment thread to find other options as there are several equally good solutions presented there.






                                      share|improve this answer




























                                        5














                                        Here's a clever solution from Github user aikoven from https://github.com/reactjs/redux/issues/992#issuecomment-191152574:



                                        type Action<TPayload> = {
                                        type: string;
                                        payload: TPayload;
                                        }

                                        interface IActionCreator<P> {
                                        type: string;
                                        (payload: P): Action<P>;
                                        }

                                        function actionCreator<P>(type: string): IActionCreator<P> {
                                        return Object.assign(
                                        (payload: P) => ({type, payload}),
                                        {type}
                                        );
                                        }

                                        function isType<P>(action: Action<any>,
                                        actionCreator: IActionCreator<P>): action is Action<P> {
                                        return action.type === actionCreator.type;
                                        }


                                        Use actionCreator<P> to define your actions and action creators:



                                        export const helloWorldAction = actionCreator<{foo: string}>('HELLO_WORLD');
                                        export const otherAction = actionCreator<{a: number, b: string}>('OTHER_ACTION');


                                        Use the user defined type guard isType<P> in the reducer:



                                        function helloReducer(state: string = ['hello'], action: Action<any>): string {
                                        if (isType(action, helloWorldAction)) { // type guard
                                        return [...state, action.payload.foo], // action.payload is now {foo: string}
                                        }
                                        else if(isType(action, otherAction)) {
                                        ...


                                        And to dispatch an action:



                                        dispatch(helloWorldAction({foo: 'world'})
                                        dispatch(otherAction({a: 42, b: 'moon'}))


                                        I recommend reading through the whole comment thread to find other options as there are several equally good solutions presented there.






                                        share|improve this answer


























                                          5












                                          5








                                          5







                                          Here's a clever solution from Github user aikoven from https://github.com/reactjs/redux/issues/992#issuecomment-191152574:



                                          type Action<TPayload> = {
                                          type: string;
                                          payload: TPayload;
                                          }

                                          interface IActionCreator<P> {
                                          type: string;
                                          (payload: P): Action<P>;
                                          }

                                          function actionCreator<P>(type: string): IActionCreator<P> {
                                          return Object.assign(
                                          (payload: P) => ({type, payload}),
                                          {type}
                                          );
                                          }

                                          function isType<P>(action: Action<any>,
                                          actionCreator: IActionCreator<P>): action is Action<P> {
                                          return action.type === actionCreator.type;
                                          }


                                          Use actionCreator<P> to define your actions and action creators:



                                          export const helloWorldAction = actionCreator<{foo: string}>('HELLO_WORLD');
                                          export const otherAction = actionCreator<{a: number, b: string}>('OTHER_ACTION');


                                          Use the user defined type guard isType<P> in the reducer:



                                          function helloReducer(state: string = ['hello'], action: Action<any>): string {
                                          if (isType(action, helloWorldAction)) { // type guard
                                          return [...state, action.payload.foo], // action.payload is now {foo: string}
                                          }
                                          else if(isType(action, otherAction)) {
                                          ...


                                          And to dispatch an action:



                                          dispatch(helloWorldAction({foo: 'world'})
                                          dispatch(otherAction({a: 42, b: 'moon'}))


                                          I recommend reading through the whole comment thread to find other options as there are several equally good solutions presented there.






                                          share|improve this answer













                                          Here's a clever solution from Github user aikoven from https://github.com/reactjs/redux/issues/992#issuecomment-191152574:



                                          type Action<TPayload> = {
                                          type: string;
                                          payload: TPayload;
                                          }

                                          interface IActionCreator<P> {
                                          type: string;
                                          (payload: P): Action<P>;
                                          }

                                          function actionCreator<P>(type: string): IActionCreator<P> {
                                          return Object.assign(
                                          (payload: P) => ({type, payload}),
                                          {type}
                                          );
                                          }

                                          function isType<P>(action: Action<any>,
                                          actionCreator: IActionCreator<P>): action is Action<P> {
                                          return action.type === actionCreator.type;
                                          }


                                          Use actionCreator<P> to define your actions and action creators:



                                          export const helloWorldAction = actionCreator<{foo: string}>('HELLO_WORLD');
                                          export const otherAction = actionCreator<{a: number, b: string}>('OTHER_ACTION');


                                          Use the user defined type guard isType<P> in the reducer:



                                          function helloReducer(state: string = ['hello'], action: Action<any>): string {
                                          if (isType(action, helloWorldAction)) { // type guard
                                          return [...state, action.payload.foo], // action.payload is now {foo: string}
                                          }
                                          else if(isType(action, otherAction)) {
                                          ...


                                          And to dispatch an action:



                                          dispatch(helloWorldAction({foo: 'world'})
                                          dispatch(otherAction({a: 42, b: 'moon'}))


                                          I recommend reading through the whole comment thread to find other options as there are several equally good solutions presented there.







                                          share|improve this answer












                                          share|improve this answer



                                          share|improve this answer










                                          answered Sep 20 '16 at 16:30









                                          Jussi KJussi K

                                          5121




                                          5121























                                              5














                                              Here is how I do it:



                                              IAction.ts



                                              import {Action} from 'redux';

                                              /**
                                              * https://github.com/acdlite/flux-standard-action
                                              */
                                              export default interface IAction<T> extends Action<string> {
                                              type: string;
                                              payload?: T;
                                              error?: boolean;
                                              meta?: any;
                                              }


                                              UserAction.ts



                                              import IAction from '../IAction';
                                              import UserModel from './models/UserModel';

                                              export type UserActionUnion = void | UserModel;

                                              export default class UserAction {

                                              public static readonly LOAD_USER: string = 'UserAction.LOAD_USER';
                                              public static readonly LOAD_USER_SUCCESS: string = 'UserAction.LOAD_USER_SUCCESS';

                                              public static loadUser(): IAction<void> {
                                              return {
                                              type: UserAction.LOAD_USER,
                                              };
                                              }

                                              public static loadUserSuccess(model: UserModel): IAction<UserModel> {
                                              return {
                                              payload: model,
                                              type: UserAction.LOAD_USER_SUCCESS,
                                              };
                                              }

                                              }


                                              UserReducer.ts



                                              import UserAction, {UserActionUnion} from './UserAction';
                                              import IUserReducerState from './IUserReducerState';
                                              import IAction from '../IAction';
                                              import UserModel from './models/UserModel';

                                              export default class UserReducer {

                                              private static readonly _initialState: IUserReducerState = {
                                              currentUser: null,
                                              isLoadingUser: false,
                                              };

                                              public static reducer(state: IUserReducerState = UserReducer._initialState, action: IAction<UserActionUnion>): IUserReducerState {
                                              switch (action.type) {
                                              case UserAction.LOAD_USER:
                                              return {
                                              ...state,
                                              isLoadingUser: true,
                                              };
                                              case UserAction.LOAD_USER_SUCCESS:
                                              return {
                                              ...state,
                                              isLoadingUser: false,
                                              currentUser: action.payload as UserModel,
                                              };
                                              default:
                                              return state;
                                              }
                                              }

                                              }


                                              IUserReducerState.ts



                                              import UserModel from './models/UserModel';

                                              export default interface IUserReducerState {
                                              readonly currentUser: UserModel;
                                              readonly isLoadingUser: boolean;
                                              }


                                              UserSaga.ts



                                              import IAction from '../IAction';
                                              import UserService from './UserService';
                                              import UserAction from './UserAction';
                                              import {put} from 'redux-saga/effects';
                                              import UserModel from './models/UserModel';

                                              export default class UserSaga {

                                              public static* loadUser(action: IAction<void> = null) {
                                              const userModel: UserModel = yield UserService.loadUser();

                                              yield put(UserAction.loadUserSuccess(userModel));
                                              }

                                              }


                                              UserService.ts



                                              import HttpUtility from '../../utilities/HttpUtility';
                                              import {AxiosResponse} from 'axios';
                                              import UserModel from './models/UserModel';
                                              import RandomUserResponseModel from './models/RandomUserResponseModel';
                                              import environment from 'environment';

                                              export default class UserService {

                                              private static _http: HttpUtility = new HttpUtility();

                                              public static async loadUser(): Promise<UserModel> {
                                              const endpoint: string = `${environment.endpointUrl.randomuser}?inc=picture,name,email,phone,id,dob`;
                                              const response: AxiosResponse = await UserService._http.get(endpoint);
                                              const randomUser = new RandomUserResponseModel(response.data);

                                              return randomUser.results[0];
                                              }

                                              }


                                              https://github.com/codeBelt/typescript-hapi-react-hot-loader-example






                                              share|improve this answer






























                                                5














                                                Here is how I do it:



                                                IAction.ts



                                                import {Action} from 'redux';

                                                /**
                                                * https://github.com/acdlite/flux-standard-action
                                                */
                                                export default interface IAction<T> extends Action<string> {
                                                type: string;
                                                payload?: T;
                                                error?: boolean;
                                                meta?: any;
                                                }


                                                UserAction.ts



                                                import IAction from '../IAction';
                                                import UserModel from './models/UserModel';

                                                export type UserActionUnion = void | UserModel;

                                                export default class UserAction {

                                                public static readonly LOAD_USER: string = 'UserAction.LOAD_USER';
                                                public static readonly LOAD_USER_SUCCESS: string = 'UserAction.LOAD_USER_SUCCESS';

                                                public static loadUser(): IAction<void> {
                                                return {
                                                type: UserAction.LOAD_USER,
                                                };
                                                }

                                                public static loadUserSuccess(model: UserModel): IAction<UserModel> {
                                                return {
                                                payload: model,
                                                type: UserAction.LOAD_USER_SUCCESS,
                                                };
                                                }

                                                }


                                                UserReducer.ts



                                                import UserAction, {UserActionUnion} from './UserAction';
                                                import IUserReducerState from './IUserReducerState';
                                                import IAction from '../IAction';
                                                import UserModel from './models/UserModel';

                                                export default class UserReducer {

                                                private static readonly _initialState: IUserReducerState = {
                                                currentUser: null,
                                                isLoadingUser: false,
                                                };

                                                public static reducer(state: IUserReducerState = UserReducer._initialState, action: IAction<UserActionUnion>): IUserReducerState {
                                                switch (action.type) {
                                                case UserAction.LOAD_USER:
                                                return {
                                                ...state,
                                                isLoadingUser: true,
                                                };
                                                case UserAction.LOAD_USER_SUCCESS:
                                                return {
                                                ...state,
                                                isLoadingUser: false,
                                                currentUser: action.payload as UserModel,
                                                };
                                                default:
                                                return state;
                                                }
                                                }

                                                }


                                                IUserReducerState.ts



                                                import UserModel from './models/UserModel';

                                                export default interface IUserReducerState {
                                                readonly currentUser: UserModel;
                                                readonly isLoadingUser: boolean;
                                                }


                                                UserSaga.ts



                                                import IAction from '../IAction';
                                                import UserService from './UserService';
                                                import UserAction from './UserAction';
                                                import {put} from 'redux-saga/effects';
                                                import UserModel from './models/UserModel';

                                                export default class UserSaga {

                                                public static* loadUser(action: IAction<void> = null) {
                                                const userModel: UserModel = yield UserService.loadUser();

                                                yield put(UserAction.loadUserSuccess(userModel));
                                                }

                                                }


                                                UserService.ts



                                                import HttpUtility from '../../utilities/HttpUtility';
                                                import {AxiosResponse} from 'axios';
                                                import UserModel from './models/UserModel';
                                                import RandomUserResponseModel from './models/RandomUserResponseModel';
                                                import environment from 'environment';

                                                export default class UserService {

                                                private static _http: HttpUtility = new HttpUtility();

                                                public static async loadUser(): Promise<UserModel> {
                                                const endpoint: string = `${environment.endpointUrl.randomuser}?inc=picture,name,email,phone,id,dob`;
                                                const response: AxiosResponse = await UserService._http.get(endpoint);
                                                const randomUser = new RandomUserResponseModel(response.data);

                                                return randomUser.results[0];
                                                }

                                                }


                                                https://github.com/codeBelt/typescript-hapi-react-hot-loader-example






                                                share|improve this answer




























                                                  5












                                                  5








                                                  5







                                                  Here is how I do it:



                                                  IAction.ts



                                                  import {Action} from 'redux';

                                                  /**
                                                  * https://github.com/acdlite/flux-standard-action
                                                  */
                                                  export default interface IAction<T> extends Action<string> {
                                                  type: string;
                                                  payload?: T;
                                                  error?: boolean;
                                                  meta?: any;
                                                  }


                                                  UserAction.ts



                                                  import IAction from '../IAction';
                                                  import UserModel from './models/UserModel';

                                                  export type UserActionUnion = void | UserModel;

                                                  export default class UserAction {

                                                  public static readonly LOAD_USER: string = 'UserAction.LOAD_USER';
                                                  public static readonly LOAD_USER_SUCCESS: string = 'UserAction.LOAD_USER_SUCCESS';

                                                  public static loadUser(): IAction<void> {
                                                  return {
                                                  type: UserAction.LOAD_USER,
                                                  };
                                                  }

                                                  public static loadUserSuccess(model: UserModel): IAction<UserModel> {
                                                  return {
                                                  payload: model,
                                                  type: UserAction.LOAD_USER_SUCCESS,
                                                  };
                                                  }

                                                  }


                                                  UserReducer.ts



                                                  import UserAction, {UserActionUnion} from './UserAction';
                                                  import IUserReducerState from './IUserReducerState';
                                                  import IAction from '../IAction';
                                                  import UserModel from './models/UserModel';

                                                  export default class UserReducer {

                                                  private static readonly _initialState: IUserReducerState = {
                                                  currentUser: null,
                                                  isLoadingUser: false,
                                                  };

                                                  public static reducer(state: IUserReducerState = UserReducer._initialState, action: IAction<UserActionUnion>): IUserReducerState {
                                                  switch (action.type) {
                                                  case UserAction.LOAD_USER:
                                                  return {
                                                  ...state,
                                                  isLoadingUser: true,
                                                  };
                                                  case UserAction.LOAD_USER_SUCCESS:
                                                  return {
                                                  ...state,
                                                  isLoadingUser: false,
                                                  currentUser: action.payload as UserModel,
                                                  };
                                                  default:
                                                  return state;
                                                  }
                                                  }

                                                  }


                                                  IUserReducerState.ts



                                                  import UserModel from './models/UserModel';

                                                  export default interface IUserReducerState {
                                                  readonly currentUser: UserModel;
                                                  readonly isLoadingUser: boolean;
                                                  }


                                                  UserSaga.ts



                                                  import IAction from '../IAction';
                                                  import UserService from './UserService';
                                                  import UserAction from './UserAction';
                                                  import {put} from 'redux-saga/effects';
                                                  import UserModel from './models/UserModel';

                                                  export default class UserSaga {

                                                  public static* loadUser(action: IAction<void> = null) {
                                                  const userModel: UserModel = yield UserService.loadUser();

                                                  yield put(UserAction.loadUserSuccess(userModel));
                                                  }

                                                  }


                                                  UserService.ts



                                                  import HttpUtility from '../../utilities/HttpUtility';
                                                  import {AxiosResponse} from 'axios';
                                                  import UserModel from './models/UserModel';
                                                  import RandomUserResponseModel from './models/RandomUserResponseModel';
                                                  import environment from 'environment';

                                                  export default class UserService {

                                                  private static _http: HttpUtility = new HttpUtility();

                                                  public static async loadUser(): Promise<UserModel> {
                                                  const endpoint: string = `${environment.endpointUrl.randomuser}?inc=picture,name,email,phone,id,dob`;
                                                  const response: AxiosResponse = await UserService._http.get(endpoint);
                                                  const randomUser = new RandomUserResponseModel(response.data);

                                                  return randomUser.results[0];
                                                  }

                                                  }


                                                  https://github.com/codeBelt/typescript-hapi-react-hot-loader-example






                                                  share|improve this answer















                                                  Here is how I do it:



                                                  IAction.ts



                                                  import {Action} from 'redux';

                                                  /**
                                                  * https://github.com/acdlite/flux-standard-action
                                                  */
                                                  export default interface IAction<T> extends Action<string> {
                                                  type: string;
                                                  payload?: T;
                                                  error?: boolean;
                                                  meta?: any;
                                                  }


                                                  UserAction.ts



                                                  import IAction from '../IAction';
                                                  import UserModel from './models/UserModel';

                                                  export type UserActionUnion = void | UserModel;

                                                  export default class UserAction {

                                                  public static readonly LOAD_USER: string = 'UserAction.LOAD_USER';
                                                  public static readonly LOAD_USER_SUCCESS: string = 'UserAction.LOAD_USER_SUCCESS';

                                                  public static loadUser(): IAction<void> {
                                                  return {
                                                  type: UserAction.LOAD_USER,
                                                  };
                                                  }

                                                  public static loadUserSuccess(model: UserModel): IAction<UserModel> {
                                                  return {
                                                  payload: model,
                                                  type: UserAction.LOAD_USER_SUCCESS,
                                                  };
                                                  }

                                                  }


                                                  UserReducer.ts



                                                  import UserAction, {UserActionUnion} from './UserAction';
                                                  import IUserReducerState from './IUserReducerState';
                                                  import IAction from '../IAction';
                                                  import UserModel from './models/UserModel';

                                                  export default class UserReducer {

                                                  private static readonly _initialState: IUserReducerState = {
                                                  currentUser: null,
                                                  isLoadingUser: false,
                                                  };

                                                  public static reducer(state: IUserReducerState = UserReducer._initialState, action: IAction<UserActionUnion>): IUserReducerState {
                                                  switch (action.type) {
                                                  case UserAction.LOAD_USER:
                                                  return {
                                                  ...state,
                                                  isLoadingUser: true,
                                                  };
                                                  case UserAction.LOAD_USER_SUCCESS:
                                                  return {
                                                  ...state,
                                                  isLoadingUser: false,
                                                  currentUser: action.payload as UserModel,
                                                  };
                                                  default:
                                                  return state;
                                                  }
                                                  }

                                                  }


                                                  IUserReducerState.ts



                                                  import UserModel from './models/UserModel';

                                                  export default interface IUserReducerState {
                                                  readonly currentUser: UserModel;
                                                  readonly isLoadingUser: boolean;
                                                  }


                                                  UserSaga.ts



                                                  import IAction from '../IAction';
                                                  import UserService from './UserService';
                                                  import UserAction from './UserAction';
                                                  import {put} from 'redux-saga/effects';
                                                  import UserModel from './models/UserModel';

                                                  export default class UserSaga {

                                                  public static* loadUser(action: IAction<void> = null) {
                                                  const userModel: UserModel = yield UserService.loadUser();

                                                  yield put(UserAction.loadUserSuccess(userModel));
                                                  }

                                                  }


                                                  UserService.ts



                                                  import HttpUtility from '../../utilities/HttpUtility';
                                                  import {AxiosResponse} from 'axios';
                                                  import UserModel from './models/UserModel';
                                                  import RandomUserResponseModel from './models/RandomUserResponseModel';
                                                  import environment from 'environment';

                                                  export default class UserService {

                                                  private static _http: HttpUtility = new HttpUtility();

                                                  public static async loadUser(): Promise<UserModel> {
                                                  const endpoint: string = `${environment.endpointUrl.randomuser}?inc=picture,name,email,phone,id,dob`;
                                                  const response: AxiosResponse = await UserService._http.get(endpoint);
                                                  const randomUser = new RandomUserResponseModel(response.data);

                                                  return randomUser.results[0];
                                                  }

                                                  }


                                                  https://github.com/codeBelt/typescript-hapi-react-hot-loader-example







                                                  share|improve this answer














                                                  share|improve this answer



                                                  share|improve this answer








                                                  edited Nov 21 '18 at 17:29

























                                                  answered Nov 21 '18 at 12:40









                                                  codeBeltcodeBelt

                                                  959911




                                                  959911























                                                      4














                                                      Two parts of the problem



                                                      Several comments above have mentioned concept/function `actionCreator´ -
                                                      take a look at redux-actions package
                                                      (and corresponding TypeScript definitions),
                                                      that solves first part of the problem:
                                                      creating action creator functions that have TypeScript type information specifying action payload type.



                                                      Second part of the problem is combining reducer functions into single reducer without boilerplate code and in a type-safe manner
                                                      (as the question was asked about TypeScript).



                                                      The solution



                                                      Combine
                                                      redux-actions
                                                      and redux-actions-ts-reducer packages:



                                                      1) Create actionCreator functions that can be used for creating action with desired type and payload when dispatching the action:



                                                      import { createAction } from 'redux-actions';

                                                      const negate = createAction('NEGATE'); // action without payload
                                                      const add = createAction<number>('ADD'); // action with payload type `number`


                                                      2) Create reducer with initial state and reducer functions for all related actions:



                                                      import { ReducerFactory } from 'redux-actions-ts-reducer';

                                                      // type of the state - not strictly needed, you could inline it as object for initial state
                                                      class SampleState {
                                                      count = 0;
                                                      }

                                                      // creating reducer that combines several reducer functions
                                                      const reducer = new ReducerFactory(new SampleState())
                                                      // `state` argument and return type is inferred based on `new ReducerFactory(initialState)`.
                                                      // Type of `action.payload` is inferred based on first argument (action creator)
                                                      .addReducer(add, (state, action) => {
                                                      return {
                                                      ...state,
                                                      count: state.count + action.payload,
                                                      };
                                                      })
                                                      // no point to add `action` argument to reducer in this case, as `action.payload` type would be `void` (and effectively useless)
                                                      .addReducer(negate, (state) => {
                                                      return {
                                                      ...state,
                                                      count: state.count * -1,
                                                      };
                                                      })
                                                      // chain as many reducer functions as you like with arbitrary payload types
                                                      ...
                                                      // Finally call this method, to create a reducer:
                                                      .toReducer();


                                                      As You can see from the comments You don't need to write any TypeScript type annotations, but all types are inferred
                                                      (so this even works with noImplicitAny TypeScript compiler option)



                                                      If You use actions from some framework that doesn't expose redux-action action creators (and You don't want to create them Yourself either)
                                                      or have legacy code that uses strings constants for action types you could add reducers for them as well:



                                                      const SOME_LIB_NO_ARGS_ACTION_TYPE = '@@some-lib/NO_ARGS_ACTION_TYPE';
                                                      const SOME_LIB_STRING_ACTION_TYPE = '@@some-lib/STRING_ACTION_TYPE';

                                                      const reducer = new ReducerFactory(new SampleState())
                                                      ...
                                                      // when adding reducer for action using string actionType
                                                      // You should tell what is the action payload type using generic argument (if You plan to use `action.payload`)
                                                      .addReducer<string>(SOME_LIB_STRING_ACTION_TYPE, (state, action) => {
                                                      return {
                                                      ...state,
                                                      message: action.payload,
                                                      };
                                                      })
                                                      // action.payload type is `void` by default when adding reducer function using `addReducer(actionType: string, reducerFunction)`
                                                      .addReducer(SOME_LIB_NO_ARGS_ACTION_TYPE, (state) => {
                                                      return new SampleState();
                                                      })
                                                      ...
                                                      .toReducer();


                                                      so it is easy to get started without refactoring Your codebase.



                                                      Dispatching actions



                                                      You can dispatch actions even without redux like this:



                                                      const newState = reducer(previousState, add(5));


                                                      but dispatching action with redux is simpler - use the dispatch(...) function as usual:



                                                      dispatch(add(5));
                                                      dispatch(negate());
                                                      dispatch({ // dispatching action without actionCreator
                                                      type: SOME_LIB_STRING_ACTION_TYPE,
                                                      payload: newMessage,
                                                      });



                                                      Confession: I'm the author of redux-actions-ts-reducer that I open-sourced today.







                                                      share|improve this answer


























                                                      • Thank you very much for this solution! This helps me a lot for my current project. =)

                                                        – fraherm
                                                        Jun 1 '18 at 19:41


















                                                      4














                                                      Two parts of the problem



                                                      Several comments above have mentioned concept/function `actionCreator´ -
                                                      take a look at redux-actions package
                                                      (and corresponding TypeScript definitions),
                                                      that solves first part of the problem:
                                                      creating action creator functions that have TypeScript type information specifying action payload type.



                                                      Second part of the problem is combining reducer functions into single reducer without boilerplate code and in a type-safe manner
                                                      (as the question was asked about TypeScript).



                                                      The solution



                                                      Combine
                                                      redux-actions
                                                      and redux-actions-ts-reducer packages:



                                                      1) Create actionCreator functions that can be used for creating action with desired type and payload when dispatching the action:



                                                      import { createAction } from 'redux-actions';

                                                      const negate = createAction('NEGATE'); // action without payload
                                                      const add = createAction<number>('ADD'); // action with payload type `number`


                                                      2) Create reducer with initial state and reducer functions for all related actions:



                                                      import { ReducerFactory } from 'redux-actions-ts-reducer';

                                                      // type of the state - not strictly needed, you could inline it as object for initial state
                                                      class SampleState {
                                                      count = 0;
                                                      }

                                                      // creating reducer that combines several reducer functions
                                                      const reducer = new ReducerFactory(new SampleState())
                                                      // `state` argument and return type is inferred based on `new ReducerFactory(initialState)`.
                                                      // Type of `action.payload` is inferred based on first argument (action creator)
                                                      .addReducer(add, (state, action) => {
                                                      return {
                                                      ...state,
                                                      count: state.count + action.payload,
                                                      };
                                                      })
                                                      // no point to add `action` argument to reducer in this case, as `action.payload` type would be `void` (and effectively useless)
                                                      .addReducer(negate, (state) => {
                                                      return {
                                                      ...state,
                                                      count: state.count * -1,
                                                      };
                                                      })
                                                      // chain as many reducer functions as you like with arbitrary payload types
                                                      ...
                                                      // Finally call this method, to create a reducer:
                                                      .toReducer();


                                                      As You can see from the comments You don't need to write any TypeScript type annotations, but all types are inferred
                                                      (so this even works with noImplicitAny TypeScript compiler option)



                                                      If You use actions from some framework that doesn't expose redux-action action creators (and You don't want to create them Yourself either)
                                                      or have legacy code that uses strings constants for action types you could add reducers for them as well:



                                                      const SOME_LIB_NO_ARGS_ACTION_TYPE = '@@some-lib/NO_ARGS_ACTION_TYPE';
                                                      const SOME_LIB_STRING_ACTION_TYPE = '@@some-lib/STRING_ACTION_TYPE';

                                                      const reducer = new ReducerFactory(new SampleState())
                                                      ...
                                                      // when adding reducer for action using string actionType
                                                      // You should tell what is the action payload type using generic argument (if You plan to use `action.payload`)
                                                      .addReducer<string>(SOME_LIB_STRING_ACTION_TYPE, (state, action) => {
                                                      return {
                                                      ...state,
                                                      message: action.payload,
                                                      };
                                                      })
                                                      // action.payload type is `void` by default when adding reducer function using `addReducer(actionType: string, reducerFunction)`
                                                      .addReducer(SOME_LIB_NO_ARGS_ACTION_TYPE, (state) => {
                                                      return new SampleState();
                                                      })
                                                      ...
                                                      .toReducer();


                                                      so it is easy to get started without refactoring Your codebase.



                                                      Dispatching actions



                                                      You can dispatch actions even without redux like this:



                                                      const newState = reducer(previousState, add(5));


                                                      but dispatching action with redux is simpler - use the dispatch(...) function as usual:



                                                      dispatch(add(5));
                                                      dispatch(negate());
                                                      dispatch({ // dispatching action without actionCreator
                                                      type: SOME_LIB_STRING_ACTION_TYPE,
                                                      payload: newMessage,
                                                      });



                                                      Confession: I'm the author of redux-actions-ts-reducer that I open-sourced today.







                                                      share|improve this answer


























                                                      • Thank you very much for this solution! This helps me a lot for my current project. =)

                                                        – fraherm
                                                        Jun 1 '18 at 19:41
















                                                      4












                                                      4








                                                      4







                                                      Two parts of the problem



                                                      Several comments above have mentioned concept/function `actionCreator´ -
                                                      take a look at redux-actions package
                                                      (and corresponding TypeScript definitions),
                                                      that solves first part of the problem:
                                                      creating action creator functions that have TypeScript type information specifying action payload type.



                                                      Second part of the problem is combining reducer functions into single reducer without boilerplate code and in a type-safe manner
                                                      (as the question was asked about TypeScript).



                                                      The solution



                                                      Combine
                                                      redux-actions
                                                      and redux-actions-ts-reducer packages:



                                                      1) Create actionCreator functions that can be used for creating action with desired type and payload when dispatching the action:



                                                      import { createAction } from 'redux-actions';

                                                      const negate = createAction('NEGATE'); // action without payload
                                                      const add = createAction<number>('ADD'); // action with payload type `number`


                                                      2) Create reducer with initial state and reducer functions for all related actions:



                                                      import { ReducerFactory } from 'redux-actions-ts-reducer';

                                                      // type of the state - not strictly needed, you could inline it as object for initial state
                                                      class SampleState {
                                                      count = 0;
                                                      }

                                                      // creating reducer that combines several reducer functions
                                                      const reducer = new ReducerFactory(new SampleState())
                                                      // `state` argument and return type is inferred based on `new ReducerFactory(initialState)`.
                                                      // Type of `action.payload` is inferred based on first argument (action creator)
                                                      .addReducer(add, (state, action) => {
                                                      return {
                                                      ...state,
                                                      count: state.count + action.payload,
                                                      };
                                                      })
                                                      // no point to add `action` argument to reducer in this case, as `action.payload` type would be `void` (and effectively useless)
                                                      .addReducer(negate, (state) => {
                                                      return {
                                                      ...state,
                                                      count: state.count * -1,
                                                      };
                                                      })
                                                      // chain as many reducer functions as you like with arbitrary payload types
                                                      ...
                                                      // Finally call this method, to create a reducer:
                                                      .toReducer();


                                                      As You can see from the comments You don't need to write any TypeScript type annotations, but all types are inferred
                                                      (so this even works with noImplicitAny TypeScript compiler option)



                                                      If You use actions from some framework that doesn't expose redux-action action creators (and You don't want to create them Yourself either)
                                                      or have legacy code that uses strings constants for action types you could add reducers for them as well:



                                                      const SOME_LIB_NO_ARGS_ACTION_TYPE = '@@some-lib/NO_ARGS_ACTION_TYPE';
                                                      const SOME_LIB_STRING_ACTION_TYPE = '@@some-lib/STRING_ACTION_TYPE';

                                                      const reducer = new ReducerFactory(new SampleState())
                                                      ...
                                                      // when adding reducer for action using string actionType
                                                      // You should tell what is the action payload type using generic argument (if You plan to use `action.payload`)
                                                      .addReducer<string>(SOME_LIB_STRING_ACTION_TYPE, (state, action) => {
                                                      return {
                                                      ...state,
                                                      message: action.payload,
                                                      };
                                                      })
                                                      // action.payload type is `void` by default when adding reducer function using `addReducer(actionType: string, reducerFunction)`
                                                      .addReducer(SOME_LIB_NO_ARGS_ACTION_TYPE, (state) => {
                                                      return new SampleState();
                                                      })
                                                      ...
                                                      .toReducer();


                                                      so it is easy to get started without refactoring Your codebase.



                                                      Dispatching actions



                                                      You can dispatch actions even without redux like this:



                                                      const newState = reducer(previousState, add(5));


                                                      but dispatching action with redux is simpler - use the dispatch(...) function as usual:



                                                      dispatch(add(5));
                                                      dispatch(negate());
                                                      dispatch({ // dispatching action without actionCreator
                                                      type: SOME_LIB_STRING_ACTION_TYPE,
                                                      payload: newMessage,
                                                      });



                                                      Confession: I'm the author of redux-actions-ts-reducer that I open-sourced today.







                                                      share|improve this answer















                                                      Two parts of the problem



                                                      Several comments above have mentioned concept/function `actionCreator´ -
                                                      take a look at redux-actions package
                                                      (and corresponding TypeScript definitions),
                                                      that solves first part of the problem:
                                                      creating action creator functions that have TypeScript type information specifying action payload type.



                                                      Second part of the problem is combining reducer functions into single reducer without boilerplate code and in a type-safe manner
                                                      (as the question was asked about TypeScript).



                                                      The solution



                                                      Combine
                                                      redux-actions
                                                      and redux-actions-ts-reducer packages:



                                                      1) Create actionCreator functions that can be used for creating action with desired type and payload when dispatching the action:



                                                      import { createAction } from 'redux-actions';

                                                      const negate = createAction('NEGATE'); // action without payload
                                                      const add = createAction<number>('ADD'); // action with payload type `number`


                                                      2) Create reducer with initial state and reducer functions for all related actions:



                                                      import { ReducerFactory } from 'redux-actions-ts-reducer';

                                                      // type of the state - not strictly needed, you could inline it as object for initial state
                                                      class SampleState {
                                                      count = 0;
                                                      }

                                                      // creating reducer that combines several reducer functions
                                                      const reducer = new ReducerFactory(new SampleState())
                                                      // `state` argument and return type is inferred based on `new ReducerFactory(initialState)`.
                                                      // Type of `action.payload` is inferred based on first argument (action creator)
                                                      .addReducer(add, (state, action) => {
                                                      return {
                                                      ...state,
                                                      count: state.count + action.payload,
                                                      };
                                                      })
                                                      // no point to add `action` argument to reducer in this case, as `action.payload` type would be `void` (and effectively useless)
                                                      .addReducer(negate, (state) => {
                                                      return {
                                                      ...state,
                                                      count: state.count * -1,
                                                      };
                                                      })
                                                      // chain as many reducer functions as you like with arbitrary payload types
                                                      ...
                                                      // Finally call this method, to create a reducer:
                                                      .toReducer();


                                                      As You can see from the comments You don't need to write any TypeScript type annotations, but all types are inferred
                                                      (so this even works with noImplicitAny TypeScript compiler option)



                                                      If You use actions from some framework that doesn't expose redux-action action creators (and You don't want to create them Yourself either)
                                                      or have legacy code that uses strings constants for action types you could add reducers for them as well:



                                                      const SOME_LIB_NO_ARGS_ACTION_TYPE = '@@some-lib/NO_ARGS_ACTION_TYPE';
                                                      const SOME_LIB_STRING_ACTION_TYPE = '@@some-lib/STRING_ACTION_TYPE';

                                                      const reducer = new ReducerFactory(new SampleState())
                                                      ...
                                                      // when adding reducer for action using string actionType
                                                      // You should tell what is the action payload type using generic argument (if You plan to use `action.payload`)
                                                      .addReducer<string>(SOME_LIB_STRING_ACTION_TYPE, (state, action) => {
                                                      return {
                                                      ...state,
                                                      message: action.payload,
                                                      };
                                                      })
                                                      // action.payload type is `void` by default when adding reducer function using `addReducer(actionType: string, reducerFunction)`
                                                      .addReducer(SOME_LIB_NO_ARGS_ACTION_TYPE, (state) => {
                                                      return new SampleState();
                                                      })
                                                      ...
                                                      .toReducer();


                                                      so it is easy to get started without refactoring Your codebase.



                                                      Dispatching actions



                                                      You can dispatch actions even without redux like this:



                                                      const newState = reducer(previousState, add(5));


                                                      but dispatching action with redux is simpler - use the dispatch(...) function as usual:



                                                      dispatch(add(5));
                                                      dispatch(negate());
                                                      dispatch({ // dispatching action without actionCreator
                                                      type: SOME_LIB_STRING_ACTION_TYPE,
                                                      payload: newMessage,
                                                      });



                                                      Confession: I'm the author of redux-actions-ts-reducer that I open-sourced today.








                                                      share|improve this answer














                                                      share|improve this answer



                                                      share|improve this answer








                                                      edited May 7 '18 at 16:58

























                                                      answered May 6 '18 at 20:43









                                                      atsu85atsu85

                                                      1043




                                                      1043













                                                      • Thank you very much for this solution! This helps me a lot for my current project. =)

                                                        – fraherm
                                                        Jun 1 '18 at 19:41





















                                                      • Thank you very much for this solution! This helps me a lot for my current project. =)

                                                        – fraherm
                                                        Jun 1 '18 at 19:41



















                                                      Thank you very much for this solution! This helps me a lot for my current project. =)

                                                      – fraherm
                                                      Jun 1 '18 at 19:41







                                                      Thank you very much for this solution! This helps me a lot for my current project. =)

                                                      – fraherm
                                                      Jun 1 '18 at 19:41













                                                      2














                                                      you could do the following things



                                                      if you expect one of IActionA or IActionB only, you can limit the type at least and define your function as



                                                      const reducer = (action: (IActionA | IActionB)) => {
                                                      ...
                                                      }


                                                      Now, the thing is, you still have to find out which type it is. You can totally add a type property but then, you have to set it somewhere, and interfaces are only overlays over object structures. You could create action classes and have the ctor set the type.



                                                      Otherwise you have to verify the object by something else.
                                                      In your case you could use hasOwnProperty and depending on that, cast it to the correct type:



                                                      const reducer = (action: (IActionA | IActionB)) => {
                                                      if(action.hasOwnProperty("a")){
                                                      return (<IActionA>action).a;
                                                      }

                                                      return (<IActionB>action).b;
                                                      }


                                                      This would still work when compiled to JavaScript.






                                                      share|improve this answer




























                                                        2














                                                        you could do the following things



                                                        if you expect one of IActionA or IActionB only, you can limit the type at least and define your function as



                                                        const reducer = (action: (IActionA | IActionB)) => {
                                                        ...
                                                        }


                                                        Now, the thing is, you still have to find out which type it is. You can totally add a type property but then, you have to set it somewhere, and interfaces are only overlays over object structures. You could create action classes and have the ctor set the type.



                                                        Otherwise you have to verify the object by something else.
                                                        In your case you could use hasOwnProperty and depending on that, cast it to the correct type:



                                                        const reducer = (action: (IActionA | IActionB)) => {
                                                        if(action.hasOwnProperty("a")){
                                                        return (<IActionA>action).a;
                                                        }

                                                        return (<IActionB>action).b;
                                                        }


                                                        This would still work when compiled to JavaScript.






                                                        share|improve this answer


























                                                          2












                                                          2








                                                          2







                                                          you could do the following things



                                                          if you expect one of IActionA or IActionB only, you can limit the type at least and define your function as



                                                          const reducer = (action: (IActionA | IActionB)) => {
                                                          ...
                                                          }


                                                          Now, the thing is, you still have to find out which type it is. You can totally add a type property but then, you have to set it somewhere, and interfaces are only overlays over object structures. You could create action classes and have the ctor set the type.



                                                          Otherwise you have to verify the object by something else.
                                                          In your case you could use hasOwnProperty and depending on that, cast it to the correct type:



                                                          const reducer = (action: (IActionA | IActionB)) => {
                                                          if(action.hasOwnProperty("a")){
                                                          return (<IActionA>action).a;
                                                          }

                                                          return (<IActionB>action).b;
                                                          }


                                                          This would still work when compiled to JavaScript.






                                                          share|improve this answer













                                                          you could do the following things



                                                          if you expect one of IActionA or IActionB only, you can limit the type at least and define your function as



                                                          const reducer = (action: (IActionA | IActionB)) => {
                                                          ...
                                                          }


                                                          Now, the thing is, you still have to find out which type it is. You can totally add a type property but then, you have to set it somewhere, and interfaces are only overlays over object structures. You could create action classes and have the ctor set the type.



                                                          Otherwise you have to verify the object by something else.
                                                          In your case you could use hasOwnProperty and depending on that, cast it to the correct type:



                                                          const reducer = (action: (IActionA | IActionB)) => {
                                                          if(action.hasOwnProperty("a")){
                                                          return (<IActionA>action).a;
                                                          }

                                                          return (<IActionB>action).b;
                                                          }


                                                          This would still work when compiled to JavaScript.







                                                          share|improve this answer












                                                          share|improve this answer



                                                          share|improve this answer










                                                          answered Feb 18 '16 at 13:35









                                                          MichaCMichaC

                                                          11.1k23149




                                                          11.1k23149























                                                              2














                                                              The solution @Jussi_K referenced is nice because it's generic.



                                                              However, I found a way that I like better, on five points:




                                                              1. It has the action properties directly on the action object, rather than in a "payload" object -- which is shorter. (though if you prefer the "payload" prop, just uncomment the extra line in the constructor)

                                                              2. It can be type-checked in reducers with a simple action.Is(Type), instead of the clunkier isType(action, createType).

                                                              3. The logic's contained within a single class, instead of spread out amonst type Action<TPayload>, interface IActionCreator<P>, function actionCreator<P>(), function isType<P>().

                                                              4. It uses simple, real classes instead of "action creators" and interfaces, which in my opinion is more readable and extensible. To create a new Action type, just do class MyAction extends Action<{myProp}> {}.

                                                              5. It ensures consistency between the class-name and type property, by just calculating type to be the class/constructor name. This adheres to the DRY principle, unlike the other solution which has both a helloWorldAction function and a HELLO_WORLD "magic string".


                                                              Anyway, to implement this alternate setup:



                                                              First, copy this generic Action class:



                                                              class Action<Payload> {
                                                              constructor(payload: Payload) {
                                                              this.type = this.constructor.name;
                                                              //this.payload = payload;
                                                              Object.assign(this, payload);
                                                              }
                                                              type: string;
                                                              payload: Payload; // stub; needed for Is() method's type-inference to work, for some reason

                                                              Is<Payload2>(actionType: new(..._)=>Action<Payload2>): this is Payload2 {
                                                              return this.type == actionType.name;
                                                              //return this instanceof actionType; // alternative
                                                              }
                                                              }


                                                              Then create your derived Action classes:



                                                              class IncreaseNumberAction extends Action<{amount: number}> {}
                                                              class DecreaseNumberAction extends Action<{amount: number}> {}


                                                              Then, to use in a reducer function:



                                                              function reducer(state, action: Action<any>) {
                                                              if (action.Is(IncreaseNumberAction))
                                                              return {...state, number: state.number + action.amount};
                                                              if (action.Is(DecreaseNumberAction))
                                                              return {...state, number: state.number - action.amount};
                                                              return state;
                                                              }


                                                              When you want to create and dispatch an action, just do:



                                                              dispatch(new IncreaseNumberAction({amount: 10}));


                                                              As with @Jussi_K's solution, each of these steps is type-safe.



                                                              EDIT



                                                              If you want the system to be compatible with anonymous action objects (eg, from legacy code, or deserialized state), you can instead use this static function in your reducers:



                                                              function IsType<Payload>(action, actionType: new(..._)=>Action<Props>): action is Payload {
                                                              return action.type == actionType.name;
                                                              }


                                                              And use it like so:



                                                              function reducer(state, action: Action<any>) {
                                                              if (IsType(action, IncreaseNumberAction))
                                                              return {...state, number: state.number + action.amount};
                                                              if (IsType(action, DecreaseNumberAction))
                                                              return {...state, number: state.number - action.amount};
                                                              return state;
                                                              }


                                                              The other option is to add the Action.Is() method onto the global Object.prototype using Object.defineProperty. This is what I'm currently doing -- though most people don't like this since it pollutes the prototype.



                                                              EDIT 2



                                                              Despite the fact that it would work anyway, Redux complains that "Actions must be plain objects. Use custom middleware for async actions.".



                                                              To fix this, you can either:




                                                              1. Remove the isPlainObject() checks in Redux.

                                                              2. Do one of the modifications in my edit above, plus add this line to the end of the Action class's constructor: (it removes the runtime link between instance and class)


                                                              Object.setPrototypeOf(this, Object.getPrototypeOf({}));





                                                              share|improve this answer






























                                                                2














                                                                The solution @Jussi_K referenced is nice because it's generic.



                                                                However, I found a way that I like better, on five points:




                                                                1. It has the action properties directly on the action object, rather than in a "payload" object -- which is shorter. (though if you prefer the "payload" prop, just uncomment the extra line in the constructor)

                                                                2. It can be type-checked in reducers with a simple action.Is(Type), instead of the clunkier isType(action, createType).

                                                                3. The logic's contained within a single class, instead of spread out amonst type Action<TPayload>, interface IActionCreator<P>, function actionCreator<P>(), function isType<P>().

                                                                4. It uses simple, real classes instead of "action creators" and interfaces, which in my opinion is more readable and extensible. To create a new Action type, just do class MyAction extends Action<{myProp}> {}.

                                                                5. It ensures consistency between the class-name and type property, by just calculating type to be the class/constructor name. This adheres to the DRY principle, unlike the other solution which has both a helloWorldAction function and a HELLO_WORLD "magic string".


                                                                Anyway, to implement this alternate setup:



                                                                First, copy this generic Action class:



                                                                class Action<Payload> {
                                                                constructor(payload: Payload) {
                                                                this.type = this.constructor.name;
                                                                //this.payload = payload;
                                                                Object.assign(this, payload);
                                                                }
                                                                type: string;
                                                                payload: Payload; // stub; needed for Is() method's type-inference to work, for some reason

                                                                Is<Payload2>(actionType: new(..._)=>Action<Payload2>): this is Payload2 {
                                                                return this.type == actionType.name;
                                                                //return this instanceof actionType; // alternative
                                                                }
                                                                }


                                                                Then create your derived Action classes:



                                                                class IncreaseNumberAction extends Action<{amount: number}> {}
                                                                class DecreaseNumberAction extends Action<{amount: number}> {}


                                                                Then, to use in a reducer function:



                                                                function reducer(state, action: Action<any>) {
                                                                if (action.Is(IncreaseNumberAction))
                                                                return {...state, number: state.number + action.amount};
                                                                if (action.Is(DecreaseNumberAction))
                                                                return {...state, number: state.number - action.amount};
                                                                return state;
                                                                }


                                                                When you want to create and dispatch an action, just do:



                                                                dispatch(new IncreaseNumberAction({amount: 10}));


                                                                As with @Jussi_K's solution, each of these steps is type-safe.



                                                                EDIT



                                                                If you want the system to be compatible with anonymous action objects (eg, from legacy code, or deserialized state), you can instead use this static function in your reducers:



                                                                function IsType<Payload>(action, actionType: new(..._)=>Action<Props>): action is Payload {
                                                                return action.type == actionType.name;
                                                                }


                                                                And use it like so:



                                                                function reducer(state, action: Action<any>) {
                                                                if (IsType(action, IncreaseNumberAction))
                                                                return {...state, number: state.number + action.amount};
                                                                if (IsType(action, DecreaseNumberAction))
                                                                return {...state, number: state.number - action.amount};
                                                                return state;
                                                                }


                                                                The other option is to add the Action.Is() method onto the global Object.prototype using Object.defineProperty. This is what I'm currently doing -- though most people don't like this since it pollutes the prototype.



                                                                EDIT 2



                                                                Despite the fact that it would work anyway, Redux complains that "Actions must be plain objects. Use custom middleware for async actions.".



                                                                To fix this, you can either:




                                                                1. Remove the isPlainObject() checks in Redux.

                                                                2. Do one of the modifications in my edit above, plus add this line to the end of the Action class's constructor: (it removes the runtime link between instance and class)


                                                                Object.setPrototypeOf(this, Object.getPrototypeOf({}));





                                                                share|improve this answer




























                                                                  2












                                                                  2








                                                                  2







                                                                  The solution @Jussi_K referenced is nice because it's generic.



                                                                  However, I found a way that I like better, on five points:




                                                                  1. It has the action properties directly on the action object, rather than in a "payload" object -- which is shorter. (though if you prefer the "payload" prop, just uncomment the extra line in the constructor)

                                                                  2. It can be type-checked in reducers with a simple action.Is(Type), instead of the clunkier isType(action, createType).

                                                                  3. The logic's contained within a single class, instead of spread out amonst type Action<TPayload>, interface IActionCreator<P>, function actionCreator<P>(), function isType<P>().

                                                                  4. It uses simple, real classes instead of "action creators" and interfaces, which in my opinion is more readable and extensible. To create a new Action type, just do class MyAction extends Action<{myProp}> {}.

                                                                  5. It ensures consistency between the class-name and type property, by just calculating type to be the class/constructor name. This adheres to the DRY principle, unlike the other solution which has both a helloWorldAction function and a HELLO_WORLD "magic string".


                                                                  Anyway, to implement this alternate setup:



                                                                  First, copy this generic Action class:



                                                                  class Action<Payload> {
                                                                  constructor(payload: Payload) {
                                                                  this.type = this.constructor.name;
                                                                  //this.payload = payload;
                                                                  Object.assign(this, payload);
                                                                  }
                                                                  type: string;
                                                                  payload: Payload; // stub; needed for Is() method's type-inference to work, for some reason

                                                                  Is<Payload2>(actionType: new(..._)=>Action<Payload2>): this is Payload2 {
                                                                  return this.type == actionType.name;
                                                                  //return this instanceof actionType; // alternative
                                                                  }
                                                                  }


                                                                  Then create your derived Action classes:



                                                                  class IncreaseNumberAction extends Action<{amount: number}> {}
                                                                  class DecreaseNumberAction extends Action<{amount: number}> {}


                                                                  Then, to use in a reducer function:



                                                                  function reducer(state, action: Action<any>) {
                                                                  if (action.Is(IncreaseNumberAction))
                                                                  return {...state, number: state.number + action.amount};
                                                                  if (action.Is(DecreaseNumberAction))
                                                                  return {...state, number: state.number - action.amount};
                                                                  return state;
                                                                  }


                                                                  When you want to create and dispatch an action, just do:



                                                                  dispatch(new IncreaseNumberAction({amount: 10}));


                                                                  As with @Jussi_K's solution, each of these steps is type-safe.



                                                                  EDIT



                                                                  If you want the system to be compatible with anonymous action objects (eg, from legacy code, or deserialized state), you can instead use this static function in your reducers:



                                                                  function IsType<Payload>(action, actionType: new(..._)=>Action<Props>): action is Payload {
                                                                  return action.type == actionType.name;
                                                                  }


                                                                  And use it like so:



                                                                  function reducer(state, action: Action<any>) {
                                                                  if (IsType(action, IncreaseNumberAction))
                                                                  return {...state, number: state.number + action.amount};
                                                                  if (IsType(action, DecreaseNumberAction))
                                                                  return {...state, number: state.number - action.amount};
                                                                  return state;
                                                                  }


                                                                  The other option is to add the Action.Is() method onto the global Object.prototype using Object.defineProperty. This is what I'm currently doing -- though most people don't like this since it pollutes the prototype.



                                                                  EDIT 2



                                                                  Despite the fact that it would work anyway, Redux complains that "Actions must be plain objects. Use custom middleware for async actions.".



                                                                  To fix this, you can either:




                                                                  1. Remove the isPlainObject() checks in Redux.

                                                                  2. Do one of the modifications in my edit above, plus add this line to the end of the Action class's constructor: (it removes the runtime link between instance and class)


                                                                  Object.setPrototypeOf(this, Object.getPrototypeOf({}));





                                                                  share|improve this answer















                                                                  The solution @Jussi_K referenced is nice because it's generic.



                                                                  However, I found a way that I like better, on five points:




                                                                  1. It has the action properties directly on the action object, rather than in a "payload" object -- which is shorter. (though if you prefer the "payload" prop, just uncomment the extra line in the constructor)

                                                                  2. It can be type-checked in reducers with a simple action.Is(Type), instead of the clunkier isType(action, createType).

                                                                  3. The logic's contained within a single class, instead of spread out amonst type Action<TPayload>, interface IActionCreator<P>, function actionCreator<P>(), function isType<P>().

                                                                  4. It uses simple, real classes instead of "action creators" and interfaces, which in my opinion is more readable and extensible. To create a new Action type, just do class MyAction extends Action<{myProp}> {}.

                                                                  5. It ensures consistency between the class-name and type property, by just calculating type to be the class/constructor name. This adheres to the DRY principle, unlike the other solution which has both a helloWorldAction function and a HELLO_WORLD "magic string".


                                                                  Anyway, to implement this alternate setup:



                                                                  First, copy this generic Action class:



                                                                  class Action<Payload> {
                                                                  constructor(payload: Payload) {
                                                                  this.type = this.constructor.name;
                                                                  //this.payload = payload;
                                                                  Object.assign(this, payload);
                                                                  }
                                                                  type: string;
                                                                  payload: Payload; // stub; needed for Is() method's type-inference to work, for some reason

                                                                  Is<Payload2>(actionType: new(..._)=>Action<Payload2>): this is Payload2 {
                                                                  return this.type == actionType.name;
                                                                  //return this instanceof actionType; // alternative
                                                                  }
                                                                  }


                                                                  Then create your derived Action classes:



                                                                  class IncreaseNumberAction extends Action<{amount: number}> {}
                                                                  class DecreaseNumberAction extends Action<{amount: number}> {}


                                                                  Then, to use in a reducer function:



                                                                  function reducer(state, action: Action<any>) {
                                                                  if (action.Is(IncreaseNumberAction))
                                                                  return {...state, number: state.number + action.amount};
                                                                  if (action.Is(DecreaseNumberAction))
                                                                  return {...state, number: state.number - action.amount};
                                                                  return state;
                                                                  }


                                                                  When you want to create and dispatch an action, just do:



                                                                  dispatch(new IncreaseNumberAction({amount: 10}));


                                                                  As with @Jussi_K's solution, each of these steps is type-safe.



                                                                  EDIT



                                                                  If you want the system to be compatible with anonymous action objects (eg, from legacy code, or deserialized state), you can instead use this static function in your reducers:



                                                                  function IsType<Payload>(action, actionType: new(..._)=>Action<Props>): action is Payload {
                                                                  return action.type == actionType.name;
                                                                  }


                                                                  And use it like so:



                                                                  function reducer(state, action: Action<any>) {
                                                                  if (IsType(action, IncreaseNumberAction))
                                                                  return {...state, number: state.number + action.amount};
                                                                  if (IsType(action, DecreaseNumberAction))
                                                                  return {...state, number: state.number - action.amount};
                                                                  return state;
                                                                  }


                                                                  The other option is to add the Action.Is() method onto the global Object.prototype using Object.defineProperty. This is what I'm currently doing -- though most people don't like this since it pollutes the prototype.



                                                                  EDIT 2



                                                                  Despite the fact that it would work anyway, Redux complains that "Actions must be plain objects. Use custom middleware for async actions.".



                                                                  To fix this, you can either:




                                                                  1. Remove the isPlainObject() checks in Redux.

                                                                  2. Do one of the modifications in my edit above, plus add this line to the end of the Action class's constructor: (it removes the runtime link between instance and class)


                                                                  Object.setPrototypeOf(this, Object.getPrototypeOf({}));






                                                                  share|improve this answer














                                                                  share|improve this answer



                                                                  share|improve this answer








                                                                  edited Mar 11 '17 at 14:45

























                                                                  answered Mar 11 '17 at 11:45









                                                                  VenryxVenryx

                                                                  2,75711926




                                                                  2,75711926























                                                                      2














                                                                      To get implicit typesafety without having to write interfaces for every action, you can use this approach (inspired by the returntypeof function from here: https://github.com/piotrwitek/react-redux-typescript#returntypeof-polyfill)



                                                                      import { values } from 'underscore'

                                                                      /**
                                                                      * action creator (declaring the return type is optional,
                                                                      * but you can make the props readonly)
                                                                      */
                                                                      export const createAction = <T extends string, P extends {}>(type: T, payload: P) => {
                                                                      return {
                                                                      type,
                                                                      payload
                                                                      } as {
                                                                      readonly type: T,
                                                                      readonly payload: P
                                                                      }
                                                                      }

                                                                      /**
                                                                      * Action types
                                                                      */
                                                                      const ACTION_A = "ACTION_A"
                                                                      const ACTION_B = "ACTION_B"

                                                                      /**
                                                                      * actions
                                                                      */
                                                                      const actions = {
                                                                      actionA: (count: number) => createAction(ACTION_A, { count }),
                                                                      actionB: (name: string) => createAction(ACTION_B, { name })
                                                                      }

                                                                      /**
                                                                      * create action type which you can use with a typeguard in the reducer
                                                                      * the actionlist variable is only needed for generation of TAction
                                                                      */
                                                                      const actionList = values(actions).map(returnTypeOf)
                                                                      type TAction = typeof actionList[number]

                                                                      /**
                                                                      * Reducer
                                                                      */
                                                                      export const reducer = (state: any, action: TAction) => {
                                                                      if ( action.type === ACTION_A ) {
                                                                      console.log(action.payload.count)
                                                                      }
                                                                      if ( action.type === ACTION_B ) {
                                                                      console.log(action.payload.name)
                                                                      console.log(action.payload.count) // compile error, because count does not exist on ACTION_B
                                                                      }
                                                                      console.log(action.payload.name) // compile error because name does not exist on every action
                                                                      }





                                                                      share|improve this answer




























                                                                        2














                                                                        To get implicit typesafety without having to write interfaces for every action, you can use this approach (inspired by the returntypeof function from here: https://github.com/piotrwitek/react-redux-typescript#returntypeof-polyfill)



                                                                        import { values } from 'underscore'

                                                                        /**
                                                                        * action creator (declaring the return type is optional,
                                                                        * but you can make the props readonly)
                                                                        */
                                                                        export const createAction = <T extends string, P extends {}>(type: T, payload: P) => {
                                                                        return {
                                                                        type,
                                                                        payload
                                                                        } as {
                                                                        readonly type: T,
                                                                        readonly payload: P
                                                                        }
                                                                        }

                                                                        /**
                                                                        * Action types
                                                                        */
                                                                        const ACTION_A = "ACTION_A"
                                                                        const ACTION_B = "ACTION_B"

                                                                        /**
                                                                        * actions
                                                                        */
                                                                        const actions = {
                                                                        actionA: (count: number) => createAction(ACTION_A, { count }),
                                                                        actionB: (name: string) => createAction(ACTION_B, { name })
                                                                        }

                                                                        /**
                                                                        * create action type which you can use with a typeguard in the reducer
                                                                        * the actionlist variable is only needed for generation of TAction
                                                                        */
                                                                        const actionList = values(actions).map(returnTypeOf)
                                                                        type TAction = typeof actionList[number]

                                                                        /**
                                                                        * Reducer
                                                                        */
                                                                        export const reducer = (state: any, action: TAction) => {
                                                                        if ( action.type === ACTION_A ) {
                                                                        console.log(action.payload.count)
                                                                        }
                                                                        if ( action.type === ACTION_B ) {
                                                                        console.log(action.payload.name)
                                                                        console.log(action.payload.count) // compile error, because count does not exist on ACTION_B
                                                                        }
                                                                        console.log(action.payload.name) // compile error because name does not exist on every action
                                                                        }





                                                                        share|improve this answer


























                                                                          2












                                                                          2








                                                                          2







                                                                          To get implicit typesafety without having to write interfaces for every action, you can use this approach (inspired by the returntypeof function from here: https://github.com/piotrwitek/react-redux-typescript#returntypeof-polyfill)



                                                                          import { values } from 'underscore'

                                                                          /**
                                                                          * action creator (declaring the return type is optional,
                                                                          * but you can make the props readonly)
                                                                          */
                                                                          export const createAction = <T extends string, P extends {}>(type: T, payload: P) => {
                                                                          return {
                                                                          type,
                                                                          payload
                                                                          } as {
                                                                          readonly type: T,
                                                                          readonly payload: P
                                                                          }
                                                                          }

                                                                          /**
                                                                          * Action types
                                                                          */
                                                                          const ACTION_A = "ACTION_A"
                                                                          const ACTION_B = "ACTION_B"

                                                                          /**
                                                                          * actions
                                                                          */
                                                                          const actions = {
                                                                          actionA: (count: number) => createAction(ACTION_A, { count }),
                                                                          actionB: (name: string) => createAction(ACTION_B, { name })
                                                                          }

                                                                          /**
                                                                          * create action type which you can use with a typeguard in the reducer
                                                                          * the actionlist variable is only needed for generation of TAction
                                                                          */
                                                                          const actionList = values(actions).map(returnTypeOf)
                                                                          type TAction = typeof actionList[number]

                                                                          /**
                                                                          * Reducer
                                                                          */
                                                                          export const reducer = (state: any, action: TAction) => {
                                                                          if ( action.type === ACTION_A ) {
                                                                          console.log(action.payload.count)
                                                                          }
                                                                          if ( action.type === ACTION_B ) {
                                                                          console.log(action.payload.name)
                                                                          console.log(action.payload.count) // compile error, because count does not exist on ACTION_B
                                                                          }
                                                                          console.log(action.payload.name) // compile error because name does not exist on every action
                                                                          }





                                                                          share|improve this answer













                                                                          To get implicit typesafety without having to write interfaces for every action, you can use this approach (inspired by the returntypeof function from here: https://github.com/piotrwitek/react-redux-typescript#returntypeof-polyfill)



                                                                          import { values } from 'underscore'

                                                                          /**
                                                                          * action creator (declaring the return type is optional,
                                                                          * but you can make the props readonly)
                                                                          */
                                                                          export const createAction = <T extends string, P extends {}>(type: T, payload: P) => {
                                                                          return {
                                                                          type,
                                                                          payload
                                                                          } as {
                                                                          readonly type: T,
                                                                          readonly payload: P
                                                                          }
                                                                          }

                                                                          /**
                                                                          * Action types
                                                                          */
                                                                          const ACTION_A = "ACTION_A"
                                                                          const ACTION_B = "ACTION_B"

                                                                          /**
                                                                          * actions
                                                                          */
                                                                          const actions = {
                                                                          actionA: (count: number) => createAction(ACTION_A, { count }),
                                                                          actionB: (name: string) => createAction(ACTION_B, { name })
                                                                          }

                                                                          /**
                                                                          * create action type which you can use with a typeguard in the reducer
                                                                          * the actionlist variable is only needed for generation of TAction
                                                                          */
                                                                          const actionList = values(actions).map(returnTypeOf)
                                                                          type TAction = typeof actionList[number]

                                                                          /**
                                                                          * Reducer
                                                                          */
                                                                          export const reducer = (state: any, action: TAction) => {
                                                                          if ( action.type === ACTION_A ) {
                                                                          console.log(action.payload.count)
                                                                          }
                                                                          if ( action.type === ACTION_B ) {
                                                                          console.log(action.payload.name)
                                                                          console.log(action.payload.count) // compile error, because count does not exist on ACTION_B
                                                                          }
                                                                          console.log(action.payload.name) // compile error because name does not exist on every action
                                                                          }






                                                                          share|improve this answer












                                                                          share|improve this answer



                                                                          share|improve this answer










                                                                          answered Jul 8 '17 at 12:21









                                                                          huesforalicehuesforalice

                                                                          1,66821827




                                                                          1,66821827























                                                                              2














                                                                              With Typescript v2, you can do this pretty easily using union types with type guards and Redux's own Action and Reducer types w/o needing to use additional 3rd party libs, and w/o enforcing a common shape to all actions (e.g. via payload).



                                                                              This way, your actions are correctly typed in your reducer catch clauses, as is the returned state.



                                                                              import {
                                                                              Action,
                                                                              Reducer,
                                                                              } from 'redux';

                                                                              interface IState {
                                                                              tinker: string
                                                                              toy: string
                                                                              }

                                                                              type IAction = ISetTinker
                                                                              | ISetToy;

                                                                              const SET_TINKER = 'SET_TINKER';
                                                                              const SET_TOY = 'SET_TOY';

                                                                              interface ISetTinker extends Action<typeof SET_TINKER> {
                                                                              tinkerValue: string
                                                                              }
                                                                              const setTinker = (tinkerValue: string): ISetTinker => ({
                                                                              type: SET_TINKER, tinkerValue,
                                                                              });
                                                                              interface ISetToy extends Action<typeof SET_TOY> {
                                                                              toyValue: string
                                                                              }
                                                                              const setToy = (toyValue: string): ISetToy => ({
                                                                              type: SET_TOY, toyValue,
                                                                              });

                                                                              const reducer: Reducer<IState, IAction> = (
                                                                              state = { tinker: 'abc', toy: 'xyz' },
                                                                              action
                                                                              ) => {
                                                                              // action is IAction
                                                                              if (action.type === SET_TINKER) {
                                                                              // action is ISetTinker
                                                                              // return { ...state, tinker: action.wrong } // doesn't typecheck
                                                                              // return { ...state, tinker: false } // doesn't typecheck
                                                                              return {
                                                                              ...state,
                                                                              tinker: action.tinkerValue,
                                                                              };
                                                                              } else if (action.type === SET_TOY) {
                                                                              return {
                                                                              ...state,
                                                                              toy: action.toyValue
                                                                              };
                                                                              }

                                                                              return state;
                                                                              }


                                                                              Things is basically what @Sven Efftinge suggests, while additionally checking the reducer's return type.






                                                                              share|improve this answer
























                                                                              • Thanks for updating @Sven Efftinge answer to use redux types!

                                                                                – jjbskir
                                                                                Nov 8 '18 at 15:31
















                                                                              2














                                                                              With Typescript v2, you can do this pretty easily using union types with type guards and Redux's own Action and Reducer types w/o needing to use additional 3rd party libs, and w/o enforcing a common shape to all actions (e.g. via payload).



                                                                              This way, your actions are correctly typed in your reducer catch clauses, as is the returned state.



                                                                              import {
                                                                              Action,
                                                                              Reducer,
                                                                              } from 'redux';

                                                                              interface IState {
                                                                              tinker: string
                                                                              toy: string
                                                                              }

                                                                              type IAction = ISetTinker
                                                                              | ISetToy;

                                                                              const SET_TINKER = 'SET_TINKER';
                                                                              const SET_TOY = 'SET_TOY';

                                                                              interface ISetTinker extends Action<typeof SET_TINKER> {
                                                                              tinkerValue: string
                                                                              }
                                                                              const setTinker = (tinkerValue: string): ISetTinker => ({
                                                                              type: SET_TINKER, tinkerValue,
                                                                              });
                                                                              interface ISetToy extends Action<typeof SET_TOY> {
                                                                              toyValue: string
                                                                              }
                                                                              const setToy = (toyValue: string): ISetToy => ({
                                                                              type: SET_TOY, toyValue,
                                                                              });

                                                                              const reducer: Reducer<IState, IAction> = (
                                                                              state = { tinker: 'abc', toy: 'xyz' },
                                                                              action
                                                                              ) => {
                                                                              // action is IAction
                                                                              if (action.type === SET_TINKER) {
                                                                              // action is ISetTinker
                                                                              // return { ...state, tinker: action.wrong } // doesn't typecheck
                                                                              // return { ...state, tinker: false } // doesn't typecheck
                                                                              return {
                                                                              ...state,
                                                                              tinker: action.tinkerValue,
                                                                              };
                                                                              } else if (action.type === SET_TOY) {
                                                                              return {
                                                                              ...state,
                                                                              toy: action.toyValue
                                                                              };
                                                                              }

                                                                              return state;
                                                                              }


                                                                              Things is basically what @Sven Efftinge suggests, while additionally checking the reducer's return type.






                                                                              share|improve this answer
























                                                                              • Thanks for updating @Sven Efftinge answer to use redux types!

                                                                                – jjbskir
                                                                                Nov 8 '18 at 15:31














                                                                              2












                                                                              2








                                                                              2







                                                                              With Typescript v2, you can do this pretty easily using union types with type guards and Redux's own Action and Reducer types w/o needing to use additional 3rd party libs, and w/o enforcing a common shape to all actions (e.g. via payload).



                                                                              This way, your actions are correctly typed in your reducer catch clauses, as is the returned state.



                                                                              import {
                                                                              Action,
                                                                              Reducer,
                                                                              } from 'redux';

                                                                              interface IState {
                                                                              tinker: string
                                                                              toy: string
                                                                              }

                                                                              type IAction = ISetTinker
                                                                              | ISetToy;

                                                                              const SET_TINKER = 'SET_TINKER';
                                                                              const SET_TOY = 'SET_TOY';

                                                                              interface ISetTinker extends Action<typeof SET_TINKER> {
                                                                              tinkerValue: string
                                                                              }
                                                                              const setTinker = (tinkerValue: string): ISetTinker => ({
                                                                              type: SET_TINKER, tinkerValue,
                                                                              });
                                                                              interface ISetToy extends Action<typeof SET_TOY> {
                                                                              toyValue: string
                                                                              }
                                                                              const setToy = (toyValue: string): ISetToy => ({
                                                                              type: SET_TOY, toyValue,
                                                                              });

                                                                              const reducer: Reducer<IState, IAction> = (
                                                                              state = { tinker: 'abc', toy: 'xyz' },
                                                                              action
                                                                              ) => {
                                                                              // action is IAction
                                                                              if (action.type === SET_TINKER) {
                                                                              // action is ISetTinker
                                                                              // return { ...state, tinker: action.wrong } // doesn't typecheck
                                                                              // return { ...state, tinker: false } // doesn't typecheck
                                                                              return {
                                                                              ...state,
                                                                              tinker: action.tinkerValue,
                                                                              };
                                                                              } else if (action.type === SET_TOY) {
                                                                              return {
                                                                              ...state,
                                                                              toy: action.toyValue
                                                                              };
                                                                              }

                                                                              return state;
                                                                              }


                                                                              Things is basically what @Sven Efftinge suggests, while additionally checking the reducer's return type.






                                                                              share|improve this answer













                                                                              With Typescript v2, you can do this pretty easily using union types with type guards and Redux's own Action and Reducer types w/o needing to use additional 3rd party libs, and w/o enforcing a common shape to all actions (e.g. via payload).



                                                                              This way, your actions are correctly typed in your reducer catch clauses, as is the returned state.



                                                                              import {
                                                                              Action,
                                                                              Reducer,
                                                                              } from 'redux';

                                                                              interface IState {
                                                                              tinker: string
                                                                              toy: string
                                                                              }

                                                                              type IAction = ISetTinker
                                                                              | ISetToy;

                                                                              const SET_TINKER = 'SET_TINKER';
                                                                              const SET_TOY = 'SET_TOY';

                                                                              interface ISetTinker extends Action<typeof SET_TINKER> {
                                                                              tinkerValue: string
                                                                              }
                                                                              const setTinker = (tinkerValue: string): ISetTinker => ({
                                                                              type: SET_TINKER, tinkerValue,
                                                                              });
                                                                              interface ISetToy extends Action<typeof SET_TOY> {
                                                                              toyValue: string
                                                                              }
                                                                              const setToy = (toyValue: string): ISetToy => ({
                                                                              type: SET_TOY, toyValue,
                                                                              });

                                                                              const reducer: Reducer<IState, IAction> = (
                                                                              state = { tinker: 'abc', toy: 'xyz' },
                                                                              action
                                                                              ) => {
                                                                              // action is IAction
                                                                              if (action.type === SET_TINKER) {
                                                                              // action is ISetTinker
                                                                              // return { ...state, tinker: action.wrong } // doesn't typecheck
                                                                              // return { ...state, tinker: false } // doesn't typecheck
                                                                              return {
                                                                              ...state,
                                                                              tinker: action.tinkerValue,
                                                                              };
                                                                              } else if (action.type === SET_TOY) {
                                                                              return {
                                                                              ...state,
                                                                              toy: action.toyValue
                                                                              };
                                                                              }

                                                                              return state;
                                                                              }


                                                                              Things is basically what @Sven Efftinge suggests, while additionally checking the reducer's return type.







                                                                              share|improve this answer












                                                                              share|improve this answer



                                                                              share|improve this answer










                                                                              answered May 31 '18 at 18:24









                                                                              James ConklingJames Conkling

                                                                              671825




                                                                              671825













                                                                              • Thanks for updating @Sven Efftinge answer to use redux types!

                                                                                – jjbskir
                                                                                Nov 8 '18 at 15:31



















                                                                              • Thanks for updating @Sven Efftinge answer to use redux types!

                                                                                – jjbskir
                                                                                Nov 8 '18 at 15:31

















                                                                              Thanks for updating @Sven Efftinge answer to use redux types!

                                                                              – jjbskir
                                                                              Nov 8 '18 at 15:31





                                                                              Thanks for updating @Sven Efftinge answer to use redux types!

                                                                              – jjbskir
                                                                              Nov 8 '18 at 15:31











                                                                              2














                                                                              I am the author of ts-redux-actions-reducer-factory and would present you this as an another solution on top of the others.
                                                                              This package infers the action by action creator or by manually defined action type and - that's new - the state. So each reducer takes aware of the return type of previous reducers and represents therefore a possible extended state that must be initialized at the end, unless done at beginning. It is kind of special in its use, but can simplify typings.



                                                                              But here a complete possible solution on base of your problem:



                                                                              import { createAction } from "redux-actions";
                                                                              import { StateType } from "typesafe-actions";
                                                                              import { ReducerFactory } from "../../src";

                                                                              // Type constants
                                                                              const aType = "a";
                                                                              const bType = "b";

                                                                              // Container a
                                                                              interface IActionA {
                                                                              a: string;
                                                                              }

                                                                              // Container b
                                                                              interface IActionB {
                                                                              b: string;
                                                                              }

                                                                              // You define the action creators:
                                                                              // - you want to be able to reduce "a"
                                                                              const createAAction = createAction<IActionA, string>(aType, (a) => ({ a }));
                                                                              // - you also want to be able to reduce "b"
                                                                              const createBAction = createAction<IActionB, string>(aType, (b) => ({ b }));

                                                                              /*
                                                                              * Now comes a neat reducer factory into the game and we
                                                                              * keep a reference to the factory for example purposes
                                                                              */
                                                                              const factory = ReducerFactory
                                                                              .create()
                                                                              /*
                                                                              * We need to take care about other following reducers, so we normally want to include the state
                                                                              * by adding "...state", otherwise only property "a" would survive after reducing "a".
                                                                              */
                                                                              .addReducer(createAAction, (state, action) => ({
                                                                              ...state,
                                                                              ...action.payload!,
                                                                              }))
                                                                              /*
                                                                              * By implementation you are forced to initialize "a", because we
                                                                              * now know about the property "a" by previous defined reducer.
                                                                              */
                                                                              .addReducer(createBAction, (state, action) => ({
                                                                              ...state,
                                                                              ...action.payload!,
                                                                              }))
                                                                              /**
                                                                              * Now we have to call `acceptUnknownState` and are forced to initialize the reducer state.
                                                                              */
                                                                              .acceptUnknownState({
                                                                              a: "I am A by default!",
                                                                              b: "I am B by default!",
                                                                              });

                                                                              // At the very end, we want the reducer.
                                                                              const reducer = factory.toReducer();

                                                                              const initialState = factory.initialKnownState;
                                                                              // { a: "I am A by default!", b: "I am B by default!" }

                                                                              const resultFromA = reducer(initialState, createAAction("I am A!"));
                                                                              // { a: "I am A!", b: "I am B by default!" }

                                                                              const resultFromB = reducer(resultFromA, createBAction("I am B!"));
                                                                              // { a: "I am A!", b: "I am B!" }

                                                                              // And when you need the new derived type, you can get it with a module like @typesafe-actions
                                                                              type DerivedType = StateType<typeof reducer>;

                                                                              // Everything is type-safe. :)
                                                                              const derivedState: DerivedType = initialState;





                                                                              share|improve this answer






























                                                                                2














                                                                                I am the author of ts-redux-actions-reducer-factory and would present you this as an another solution on top of the others.
                                                                                This package infers the action by action creator or by manually defined action type and - that's new - the state. So each reducer takes aware of the return type of previous reducers and represents therefore a possible extended state that must be initialized at the end, unless done at beginning. It is kind of special in its use, but can simplify typings.



                                                                                But here a complete possible solution on base of your problem:



                                                                                import { createAction } from "redux-actions";
                                                                                import { StateType } from "typesafe-actions";
                                                                                import { ReducerFactory } from "../../src";

                                                                                // Type constants
                                                                                const aType = "a";
                                                                                const bType = "b";

                                                                                // Container a
                                                                                interface IActionA {
                                                                                a: string;
                                                                                }

                                                                                // Container b
                                                                                interface IActionB {
                                                                                b: string;
                                                                                }

                                                                                // You define the action creators:
                                                                                // - you want to be able to reduce "a"
                                                                                const createAAction = createAction<IActionA, string>(aType, (a) => ({ a }));
                                                                                // - you also want to be able to reduce "b"
                                                                                const createBAction = createAction<IActionB, string>(aType, (b) => ({ b }));

                                                                                /*
                                                                                * Now comes a neat reducer factory into the game and we
                                                                                * keep a reference to the factory for example purposes
                                                                                */
                                                                                const factory = ReducerFactory
                                                                                .create()
                                                                                /*
                                                                                * We need to take care about other following reducers, so we normally want to include the state
                                                                                * by adding "...state", otherwise only property "a" would survive after reducing "a".
                                                                                */
                                                                                .addReducer(createAAction, (state, action) => ({
                                                                                ...state,
                                                                                ...action.payload!,
                                                                                }))
                                                                                /*
                                                                                * By implementation you are forced to initialize "a", because we
                                                                                * now know about the property "a" by previous defined reducer.
                                                                                */
                                                                                .addReducer(createBAction, (state, action) => ({
                                                                                ...state,
                                                                                ...action.payload!,
                                                                                }))
                                                                                /**
                                                                                * Now we have to call `acceptUnknownState` and are forced to initialize the reducer state.
                                                                                */
                                                                                .acceptUnknownState({
                                                                                a: "I am A by default!",
                                                                                b: "I am B by default!",
                                                                                });

                                                                                // At the very end, we want the reducer.
                                                                                const reducer = factory.toReducer();

                                                                                const initialState = factory.initialKnownState;
                                                                                // { a: "I am A by default!", b: "I am B by default!" }

                                                                                const resultFromA = reducer(initialState, createAAction("I am A!"));
                                                                                // { a: "I am A!", b: "I am B by default!" }

                                                                                const resultFromB = reducer(resultFromA, createBAction("I am B!"));
                                                                                // { a: "I am A!", b: "I am B!" }

                                                                                // And when you need the new derived type, you can get it with a module like @typesafe-actions
                                                                                type DerivedType = StateType<typeof reducer>;

                                                                                // Everything is type-safe. :)
                                                                                const derivedState: DerivedType = initialState;





                                                                                share|improve this answer




























                                                                                  2












                                                                                  2








                                                                                  2







                                                                                  I am the author of ts-redux-actions-reducer-factory and would present you this as an another solution on top of the others.
                                                                                  This package infers the action by action creator or by manually defined action type and - that's new - the state. So each reducer takes aware of the return type of previous reducers and represents therefore a possible extended state that must be initialized at the end, unless done at beginning. It is kind of special in its use, but can simplify typings.



                                                                                  But here a complete possible solution on base of your problem:



                                                                                  import { createAction } from "redux-actions";
                                                                                  import { StateType } from "typesafe-actions";
                                                                                  import { ReducerFactory } from "../../src";

                                                                                  // Type constants
                                                                                  const aType = "a";
                                                                                  const bType = "b";

                                                                                  // Container a
                                                                                  interface IActionA {
                                                                                  a: string;
                                                                                  }

                                                                                  // Container b
                                                                                  interface IActionB {
                                                                                  b: string;
                                                                                  }

                                                                                  // You define the action creators:
                                                                                  // - you want to be able to reduce "a"
                                                                                  const createAAction = createAction<IActionA, string>(aType, (a) => ({ a }));
                                                                                  // - you also want to be able to reduce "b"
                                                                                  const createBAction = createAction<IActionB, string>(aType, (b) => ({ b }));

                                                                                  /*
                                                                                  * Now comes a neat reducer factory into the game and we
                                                                                  * keep a reference to the factory for example purposes
                                                                                  */
                                                                                  const factory = ReducerFactory
                                                                                  .create()
                                                                                  /*
                                                                                  * We need to take care about other following reducers, so we normally want to include the state
                                                                                  * by adding "...state", otherwise only property "a" would survive after reducing "a".
                                                                                  */
                                                                                  .addReducer(createAAction, (state, action) => ({
                                                                                  ...state,
                                                                                  ...action.payload!,
                                                                                  }))
                                                                                  /*
                                                                                  * By implementation you are forced to initialize "a", because we
                                                                                  * now know about the property "a" by previous defined reducer.
                                                                                  */
                                                                                  .addReducer(createBAction, (state, action) => ({
                                                                                  ...state,
                                                                                  ...action.payload!,
                                                                                  }))
                                                                                  /**
                                                                                  * Now we have to call `acceptUnknownState` and are forced to initialize the reducer state.
                                                                                  */
                                                                                  .acceptUnknownState({
                                                                                  a: "I am A by default!",
                                                                                  b: "I am B by default!",
                                                                                  });

                                                                                  // At the very end, we want the reducer.
                                                                                  const reducer = factory.toReducer();

                                                                                  const initialState = factory.initialKnownState;
                                                                                  // { a: "I am A by default!", b: "I am B by default!" }

                                                                                  const resultFromA = reducer(initialState, createAAction("I am A!"));
                                                                                  // { a: "I am A!", b: "I am B by default!" }

                                                                                  const resultFromB = reducer(resultFromA, createBAction("I am B!"));
                                                                                  // { a: "I am A!", b: "I am B!" }

                                                                                  // And when you need the new derived type, you can get it with a module like @typesafe-actions
                                                                                  type DerivedType = StateType<typeof reducer>;

                                                                                  // Everything is type-safe. :)
                                                                                  const derivedState: DerivedType = initialState;





                                                                                  share|improve this answer















                                                                                  I am the author of ts-redux-actions-reducer-factory and would present you this as an another solution on top of the others.
                                                                                  This package infers the action by action creator or by manually defined action type and - that's new - the state. So each reducer takes aware of the return type of previous reducers and represents therefore a possible extended state that must be initialized at the end, unless done at beginning. It is kind of special in its use, but can simplify typings.



                                                                                  But here a complete possible solution on base of your problem:



                                                                                  import { createAction } from "redux-actions";
                                                                                  import { StateType } from "typesafe-actions";
                                                                                  import { ReducerFactory } from "../../src";

                                                                                  // Type constants
                                                                                  const aType = "a";
                                                                                  const bType = "b";

                                                                                  // Container a
                                                                                  interface IActionA {
                                                                                  a: string;
                                                                                  }

                                                                                  // Container b
                                                                                  interface IActionB {
                                                                                  b: string;
                                                                                  }

                                                                                  // You define the action creators:
                                                                                  // - you want to be able to reduce "a"
                                                                                  const createAAction = createAction<IActionA, string>(aType, (a) => ({ a }));
                                                                                  // - you also want to be able to reduce "b"
                                                                                  const createBAction = createAction<IActionB, string>(aType, (b) => ({ b }));

                                                                                  /*
                                                                                  * Now comes a neat reducer factory into the game and we
                                                                                  * keep a reference to the factory for example purposes
                                                                                  */
                                                                                  const factory = ReducerFactory
                                                                                  .create()
                                                                                  /*
                                                                                  * We need to take care about other following reducers, so we normally want to include the state
                                                                                  * by adding "...state", otherwise only property "a" would survive after reducing "a".
                                                                                  */
                                                                                  .addReducer(createAAction, (state, action) => ({
                                                                                  ...state,
                                                                                  ...action.payload!,
                                                                                  }))
                                                                                  /*
                                                                                  * By implementation you are forced to initialize "a", because we
                                                                                  * now know about the property "a" by previous defined reducer.
                                                                                  */
                                                                                  .addReducer(createBAction, (state, action) => ({
                                                                                  ...state,
                                                                                  ...action.payload!,
                                                                                  }))
                                                                                  /**
                                                                                  * Now we have to call `acceptUnknownState` and are forced to initialize the reducer state.
                                                                                  */
                                                                                  .acceptUnknownState({
                                                                                  a: "I am A by default!",
                                                                                  b: "I am B by default!",
                                                                                  });

                                                                                  // At the very end, we want the reducer.
                                                                                  const reducer = factory.toReducer();

                                                                                  const initialState = factory.initialKnownState;
                                                                                  // { a: "I am A by default!", b: "I am B by default!" }

                                                                                  const resultFromA = reducer(initialState, createAAction("I am A!"));
                                                                                  // { a: "I am A!", b: "I am B by default!" }

                                                                                  const resultFromB = reducer(resultFromA, createBAction("I am B!"));
                                                                                  // { a: "I am A!", b: "I am B!" }

                                                                                  // And when you need the new derived type, you can get it with a module like @typesafe-actions
                                                                                  type DerivedType = StateType<typeof reducer>;

                                                                                  // Everything is type-safe. :)
                                                                                  const derivedState: DerivedType = initialState;






                                                                                  share|improve this answer














                                                                                  share|improve this answer



                                                                                  share|improve this answer








                                                                                  edited Feb 19 at 8:57

























                                                                                  answered Feb 11 at 8:33









                                                                                  teronekoteroneko

                                                                                  212




                                                                                  212























                                                                                      1














                                                                                      you can define your action something like:



                                                                                      // src/actions/index.tsx
                                                                                      import * as constants from '../constants'

                                                                                      export interface IncrementEnthusiasm {
                                                                                      type: constants.INCREMENT_ENTHUSIASM;
                                                                                      }

                                                                                      export interface DecrementEnthusiasm {
                                                                                      type: constants.DECREMENT_ENTHUSIASM;
                                                                                      }

                                                                                      export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;

                                                                                      export function incrementEnthusiasm(): IncrementEnthusiasm {
                                                                                      return {
                                                                                      type: constants.INCREMENT_ENTHUSIASM
                                                                                      }
                                                                                      }

                                                                                      export function decrementEnthusiasm(): DecrementEnthusiasm {
                                                                                      return {
                                                                                      type: constants.DECREMENT_ENTHUSIASM
                                                                                      }
                                                                                      }


                                                                                      and so, you can define your reducer like follows:



                                                                                      // src/reducers/index.tsx



                                                                                      import { EnthusiasmAction } from '../actions';
                                                                                      import { StoreState } from '../types/index';
                                                                                      import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';

                                                                                      export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
                                                                                      switch (action.type) {
                                                                                      case INCREMENT_ENTHUSIASM:
                                                                                      return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
                                                                                      case DECREMENT_ENTHUSIASM:
                                                                                      return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
                                                                                      }
                                                                                      return state;
                                                                                      }


                                                                                      Complete official docs: https://github.com/Microsoft/TypeScript-React-Starter#adding-a-reducer






                                                                                      share|improve this answer




























                                                                                        1














                                                                                        you can define your action something like:



                                                                                        // src/actions/index.tsx
                                                                                        import * as constants from '../constants'

                                                                                        export interface IncrementEnthusiasm {
                                                                                        type: constants.INCREMENT_ENTHUSIASM;
                                                                                        }

                                                                                        export interface DecrementEnthusiasm {
                                                                                        type: constants.DECREMENT_ENTHUSIASM;
                                                                                        }

                                                                                        export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;

                                                                                        export function incrementEnthusiasm(): IncrementEnthusiasm {
                                                                                        return {
                                                                                        type: constants.INCREMENT_ENTHUSIASM
                                                                                        }
                                                                                        }

                                                                                        export function decrementEnthusiasm(): DecrementEnthusiasm {
                                                                                        return {
                                                                                        type: constants.DECREMENT_ENTHUSIASM
                                                                                        }
                                                                                        }


                                                                                        and so, you can define your reducer like follows:



                                                                                        // src/reducers/index.tsx



                                                                                        import { EnthusiasmAction } from '../actions';
                                                                                        import { StoreState } from '../types/index';
                                                                                        import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';

                                                                                        export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
                                                                                        switch (action.type) {
                                                                                        case INCREMENT_ENTHUSIASM:
                                                                                        return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
                                                                                        case DECREMENT_ENTHUSIASM:
                                                                                        return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
                                                                                        }
                                                                                        return state;
                                                                                        }


                                                                                        Complete official docs: https://github.com/Microsoft/TypeScript-React-Starter#adding-a-reducer






                                                                                        share|improve this answer


























                                                                                          1












                                                                                          1








                                                                                          1







                                                                                          you can define your action something like:



                                                                                          // src/actions/index.tsx
                                                                                          import * as constants from '../constants'

                                                                                          export interface IncrementEnthusiasm {
                                                                                          type: constants.INCREMENT_ENTHUSIASM;
                                                                                          }

                                                                                          export interface DecrementEnthusiasm {
                                                                                          type: constants.DECREMENT_ENTHUSIASM;
                                                                                          }

                                                                                          export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;

                                                                                          export function incrementEnthusiasm(): IncrementEnthusiasm {
                                                                                          return {
                                                                                          type: constants.INCREMENT_ENTHUSIASM
                                                                                          }
                                                                                          }

                                                                                          export function decrementEnthusiasm(): DecrementEnthusiasm {
                                                                                          return {
                                                                                          type: constants.DECREMENT_ENTHUSIASM
                                                                                          }
                                                                                          }


                                                                                          and so, you can define your reducer like follows:



                                                                                          // src/reducers/index.tsx



                                                                                          import { EnthusiasmAction } from '../actions';
                                                                                          import { StoreState } from '../types/index';
                                                                                          import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';

                                                                                          export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
                                                                                          switch (action.type) {
                                                                                          case INCREMENT_ENTHUSIASM:
                                                                                          return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
                                                                                          case DECREMENT_ENTHUSIASM:
                                                                                          return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
                                                                                          }
                                                                                          return state;
                                                                                          }


                                                                                          Complete official docs: https://github.com/Microsoft/TypeScript-React-Starter#adding-a-reducer






                                                                                          share|improve this answer













                                                                                          you can define your action something like:



                                                                                          // src/actions/index.tsx
                                                                                          import * as constants from '../constants'

                                                                                          export interface IncrementEnthusiasm {
                                                                                          type: constants.INCREMENT_ENTHUSIASM;
                                                                                          }

                                                                                          export interface DecrementEnthusiasm {
                                                                                          type: constants.DECREMENT_ENTHUSIASM;
                                                                                          }

                                                                                          export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;

                                                                                          export function incrementEnthusiasm(): IncrementEnthusiasm {
                                                                                          return {
                                                                                          type: constants.INCREMENT_ENTHUSIASM
                                                                                          }
                                                                                          }

                                                                                          export function decrementEnthusiasm(): DecrementEnthusiasm {
                                                                                          return {
                                                                                          type: constants.DECREMENT_ENTHUSIASM
                                                                                          }
                                                                                          }


                                                                                          and so, you can define your reducer like follows:



                                                                                          // src/reducers/index.tsx



                                                                                          import { EnthusiasmAction } from '../actions';
                                                                                          import { StoreState } from '../types/index';
                                                                                          import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';

                                                                                          export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
                                                                                          switch (action.type) {
                                                                                          case INCREMENT_ENTHUSIASM:
                                                                                          return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
                                                                                          case DECREMENT_ENTHUSIASM:
                                                                                          return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
                                                                                          }
                                                                                          return state;
                                                                                          }


                                                                                          Complete official docs: https://github.com/Microsoft/TypeScript-React-Starter#adding-a-reducer







                                                                                          share|improve this answer












                                                                                          share|improve this answer



                                                                                          share|improve this answer










                                                                                          answered Aug 25 '17 at 0:27









                                                                                          eriknykeriknyk

                                                                                          215




                                                                                          215























                                                                                              1














                                                                                              If you need to fix your implementation exactly as you posted, this is the way how to fix it and get it working using type assertions , respectively as I show in the following:



                                                                                              interface IAction {
                                                                                              type: string
                                                                                              }

                                                                                              interface IActionA extends IAction {
                                                                                              a: string
                                                                                              }

                                                                                              interface IActionB extends IAction {
                                                                                              b: string
                                                                                              }

                                                                                              const reducer = (action: IAction) => {
                                                                                              switch (action.type) {
                                                                                              case 'a':
                                                                                              return console.info('action a: ', (<IActionA>action).a) // property 'a' exists because you're using type assertion <IActionA>

                                                                                              case 'b':
                                                                                              return console.info('action b: ', (<IActionB>action).b) // property 'b' exists because you're using type assertion <IActionB>
                                                                                              }
                                                                                              }


                                                                                              You can learn more on section "Type Guards and Differentiating Types"
                                                                                              of the official documentation: https://www.typescriptlang.org/docs/handbook/advanced-types.html






                                                                                              share|improve this answer






























                                                                                                1














                                                                                                If you need to fix your implementation exactly as you posted, this is the way how to fix it and get it working using type assertions , respectively as I show in the following:



                                                                                                interface IAction {
                                                                                                type: string
                                                                                                }

                                                                                                interface IActionA extends IAction {
                                                                                                a: string
                                                                                                }

                                                                                                interface IActionB extends IAction {
                                                                                                b: string
                                                                                                }

                                                                                                const reducer = (action: IAction) => {
                                                                                                switch (action.type) {
                                                                                                case 'a':
                                                                                                return console.info('action a: ', (<IActionA>action).a) // property 'a' exists because you're using type assertion <IActionA>

                                                                                                case 'b':
                                                                                                return console.info('action b: ', (<IActionB>action).b) // property 'b' exists because you're using type assertion <IActionB>
                                                                                                }
                                                                                                }


                                                                                                You can learn more on section "Type Guards and Differentiating Types"
                                                                                                of the official documentation: https://www.typescriptlang.org/docs/handbook/advanced-types.html






                                                                                                share|improve this answer




























                                                                                                  1












                                                                                                  1








                                                                                                  1







                                                                                                  If you need to fix your implementation exactly as you posted, this is the way how to fix it and get it working using type assertions , respectively as I show in the following:



                                                                                                  interface IAction {
                                                                                                  type: string
                                                                                                  }

                                                                                                  interface IActionA extends IAction {
                                                                                                  a: string
                                                                                                  }

                                                                                                  interface IActionB extends IAction {
                                                                                                  b: string
                                                                                                  }

                                                                                                  const reducer = (action: IAction) => {
                                                                                                  switch (action.type) {
                                                                                                  case 'a':
                                                                                                  return console.info('action a: ', (<IActionA>action).a) // property 'a' exists because you're using type assertion <IActionA>

                                                                                                  case 'b':
                                                                                                  return console.info('action b: ', (<IActionB>action).b) // property 'b' exists because you're using type assertion <IActionB>
                                                                                                  }
                                                                                                  }


                                                                                                  You can learn more on section "Type Guards and Differentiating Types"
                                                                                                  of the official documentation: https://www.typescriptlang.org/docs/handbook/advanced-types.html






                                                                                                  share|improve this answer















                                                                                                  If you need to fix your implementation exactly as you posted, this is the way how to fix it and get it working using type assertions , respectively as I show in the following:



                                                                                                  interface IAction {
                                                                                                  type: string
                                                                                                  }

                                                                                                  interface IActionA extends IAction {
                                                                                                  a: string
                                                                                                  }

                                                                                                  interface IActionB extends IAction {
                                                                                                  b: string
                                                                                                  }

                                                                                                  const reducer = (action: IAction) => {
                                                                                                  switch (action.type) {
                                                                                                  case 'a':
                                                                                                  return console.info('action a: ', (<IActionA>action).a) // property 'a' exists because you're using type assertion <IActionA>

                                                                                                  case 'b':
                                                                                                  return console.info('action b: ', (<IActionB>action).b) // property 'b' exists because you're using type assertion <IActionB>
                                                                                                  }
                                                                                                  }


                                                                                                  You can learn more on section "Type Guards and Differentiating Types"
                                                                                                  of the official documentation: https://www.typescriptlang.org/docs/handbook/advanced-types.html







                                                                                                  share|improve this answer














                                                                                                  share|improve this answer



                                                                                                  share|improve this answer








                                                                                                  edited Aug 29 '17 at 2:48

























                                                                                                  answered Aug 29 '17 at 2:41









                                                                                                  eriknykeriknyk

                                                                                                  215




                                                                                                  215























                                                                                                      1














                                                                                                      To be fair there are many ways to type actions but I find this one very straight forward and has the less possible boilerplate as well (already discussed in this topic).



                                                                                                      This approach tries to type the key called "payload" of actions.



                                                                                                      Check this sample






                                                                                                      share|improve this answer




























                                                                                                        1














                                                                                                        To be fair there are many ways to type actions but I find this one very straight forward and has the less possible boilerplate as well (already discussed in this topic).



                                                                                                        This approach tries to type the key called "payload" of actions.



                                                                                                        Check this sample






                                                                                                        share|improve this answer


























                                                                                                          1












                                                                                                          1








                                                                                                          1







                                                                                                          To be fair there are many ways to type actions but I find this one very straight forward and has the less possible boilerplate as well (already discussed in this topic).



                                                                                                          This approach tries to type the key called "payload" of actions.



                                                                                                          Check this sample






                                                                                                          share|improve this answer













                                                                                                          To be fair there are many ways to type actions but I find this one very straight forward and has the less possible boilerplate as well (already discussed in this topic).



                                                                                                          This approach tries to type the key called "payload" of actions.



                                                                                                          Check this sample







                                                                                                          share|improve this answer












                                                                                                          share|improve this answer



                                                                                                          share|improve this answer










                                                                                                          answered Oct 13 '17 at 3:21









                                                                                                          PRAISERPRAISER

                                                                                                          548212




                                                                                                          548212























                                                                                                              1














                                                                                                              Lately I have been using this approach:



                                                                                                              export abstract class PlainAction {
                                                                                                              public abstract readonly type: any;
                                                                                                              constructor() {
                                                                                                              return Object.assign({}, this);
                                                                                                              }
                                                                                                              }

                                                                                                              export abstract class ActionWithPayload<P extends object = any> extends PlainAction {
                                                                                                              constructor(public readonly payload: P) {
                                                                                                              super();
                                                                                                              }
                                                                                                              }

                                                                                                              export class BeginBusyAction extends PlainAction {
                                                                                                              public readonly type = "BeginBusy";
                                                                                                              }

                                                                                                              export interface SendChannelMessageActionPayload {
                                                                                                              message: string;
                                                                                                              }

                                                                                                              export class SendChannelMessageAction
                                                                                                              extends ActionWithPayload<SendChannelMessageActionPayload>
                                                                                                              {
                                                                                                              public readonly type = "SendChannelMessage";
                                                                                                              constructor(
                                                                                                              message: string,
                                                                                                              ) {
                                                                                                              super({
                                                                                                              message,
                                                                                                              });
                                                                                                              }
                                                                                                              }


                                                                                                              This here:



                                                                                                              constructor() {
                                                                                                              return Object.assign({}, this);
                                                                                                              }


                                                                                                              ensures that the Actions are all plain objects. Now you can make actions like this: const action = new BeginBusyAction(). (yay o/)






                                                                                                              share|improve this answer




























                                                                                                                1














                                                                                                                Lately I have been using this approach:



                                                                                                                export abstract class PlainAction {
                                                                                                                public abstract readonly type: any;
                                                                                                                constructor() {
                                                                                                                return Object.assign({}, this);
                                                                                                                }
                                                                                                                }

                                                                                                                export abstract class ActionWithPayload<P extends object = any> extends PlainAction {
                                                                                                                constructor(public readonly payload: P) {
                                                                                                                super();
                                                                                                                }
                                                                                                                }

                                                                                                                export class BeginBusyAction extends PlainAction {
                                                                                                                public readonly type = "BeginBusy";
                                                                                                                }

                                                                                                                export interface SendChannelMessageActionPayload {
                                                                                                                message: string;
                                                                                                                }

                                                                                                                export class SendChannelMessageAction
                                                                                                                extends ActionWithPayload<SendChannelMessageActionPayload>
                                                                                                                {
                                                                                                                public readonly type = "SendChannelMessage";
                                                                                                                constructor(
                                                                                                                message: string,
                                                                                                                ) {
                                                                                                                super({
                                                                                                                message,
                                                                                                                });
                                                                                                                }
                                                                                                                }


                                                                                                                This here:



                                                                                                                constructor() {
                                                                                                                return Object.assign({}, this);
                                                                                                                }


                                                                                                                ensures that the Actions are all plain objects. Now you can make actions like this: const action = new BeginBusyAction(). (yay o/)






                                                                                                                share|improve this answer


























                                                                                                                  1












                                                                                                                  1








                                                                                                                  1







                                                                                                                  Lately I have been using this approach:



                                                                                                                  export abstract class PlainAction {
                                                                                                                  public abstract readonly type: any;
                                                                                                                  constructor() {
                                                                                                                  return Object.assign({}, this);
                                                                                                                  }
                                                                                                                  }

                                                                                                                  export abstract class ActionWithPayload<P extends object = any> extends PlainAction {
                                                                                                                  constructor(public readonly payload: P) {
                                                                                                                  super();
                                                                                                                  }
                                                                                                                  }

                                                                                                                  export class BeginBusyAction extends PlainAction {
                                                                                                                  public readonly type = "BeginBusy";
                                                                                                                  }

                                                                                                                  export interface SendChannelMessageActionPayload {
                                                                                                                  message: string;
                                                                                                                  }

                                                                                                                  export class SendChannelMessageAction
                                                                                                                  extends ActionWithPayload<SendChannelMessageActionPayload>
                                                                                                                  {
                                                                                                                  public readonly type = "SendChannelMessage";
                                                                                                                  constructor(
                                                                                                                  message: string,
                                                                                                                  ) {
                                                                                                                  super({
                                                                                                                  message,
                                                                                                                  });
                                                                                                                  }
                                                                                                                  }


                                                                                                                  This here:



                                                                                                                  constructor() {
                                                                                                                  return Object.assign({}, this);
                                                                                                                  }


                                                                                                                  ensures that the Actions are all plain objects. Now you can make actions like this: const action = new BeginBusyAction(). (yay o/)






                                                                                                                  share|improve this answer













                                                                                                                  Lately I have been using this approach:



                                                                                                                  export abstract class PlainAction {
                                                                                                                  public abstract readonly type: any;
                                                                                                                  constructor() {
                                                                                                                  return Object.assign({}, this);
                                                                                                                  }
                                                                                                                  }

                                                                                                                  export abstract class ActionWithPayload<P extends object = any> extends PlainAction {
                                                                                                                  constructor(public readonly payload: P) {
                                                                                                                  super();
                                                                                                                  }
                                                                                                                  }

                                                                                                                  export class BeginBusyAction extends PlainAction {
                                                                                                                  public readonly type = "BeginBusy";
                                                                                                                  }

                                                                                                                  export interface SendChannelMessageActionPayload {
                                                                                                                  message: string;
                                                                                                                  }

                                                                                                                  export class SendChannelMessageAction
                                                                                                                  extends ActionWithPayload<SendChannelMessageActionPayload>
                                                                                                                  {
                                                                                                                  public readonly type = "SendChannelMessage";
                                                                                                                  constructor(
                                                                                                                  message: string,
                                                                                                                  ) {
                                                                                                                  super({
                                                                                                                  message,
                                                                                                                  });
                                                                                                                  }
                                                                                                                  }


                                                                                                                  This here:



                                                                                                                  constructor() {
                                                                                                                  return Object.assign({}, this);
                                                                                                                  }


                                                                                                                  ensures that the Actions are all plain objects. Now you can make actions like this: const action = new BeginBusyAction(). (yay o/)







                                                                                                                  share|improve this answer












                                                                                                                  share|improve this answer



                                                                                                                  share|improve this answer










                                                                                                                  answered Jan 6 '18 at 15:43









                                                                                                                  ElmerElmer

                                                                                                                  6,55413434




                                                                                                                  6,55413434























                                                                                                                      1














                                                                                                                      There are libraries that bundle most of the code mentioned in other answers: aikoven/typescript-fsa and dphilipson/typescript-fsa-reducers.



                                                                                                                      With these libraries all your actions and reducers code is statically typed and readable:



                                                                                                                      import actionCreatorFactory from "typescript-fsa";
                                                                                                                      const actionCreator = actionCreatorFactory();

                                                                                                                      interface State {
                                                                                                                      name: string;
                                                                                                                      balance: number;
                                                                                                                      isFrozen: boolean;
                                                                                                                      }

                                                                                                                      const INITIAL_STATE: State = {
                                                                                                                      name: "Untitled",
                                                                                                                      balance: 0,
                                                                                                                      isFrozen: false,
                                                                                                                      };

                                                                                                                      const setName = actionCreator<string>("SET_NAME");
                                                                                                                      const addBalance = actionCreator<number>("ADD_BALANCE");
                                                                                                                      const setIsFrozen = actionCreator<boolean>("SET_IS_FROZEN");

                                                                                                                      ...

                                                                                                                      import { reducerWithInitialState } from "typescript-fsa-reducers";

                                                                                                                      const reducer = reducerWithInitialState(INITIAL_STATE)
                                                                                                                      .case(setName, (state, name) => ({ ...state, name }))
                                                                                                                      .case(addBalance, (state, amount) => ({
                                                                                                                      ...state,
                                                                                                                      balance: state.balance + amount,
                                                                                                                      }))
                                                                                                                      .case(setIsFrozen, (state, isFrozen) => ({ ...state, isFrozen }));





                                                                                                                      share|improve this answer






























                                                                                                                        1














                                                                                                                        There are libraries that bundle most of the code mentioned in other answers: aikoven/typescript-fsa and dphilipson/typescript-fsa-reducers.



                                                                                                                        With these libraries all your actions and reducers code is statically typed and readable:



                                                                                                                        import actionCreatorFactory from "typescript-fsa";
                                                                                                                        const actionCreator = actionCreatorFactory();

                                                                                                                        interface State {
                                                                                                                        name: string;
                                                                                                                        balance: number;
                                                                                                                        isFrozen: boolean;
                                                                                                                        }

                                                                                                                        const INITIAL_STATE: State = {
                                                                                                                        name: "Untitled",
                                                                                                                        balance: 0,
                                                                                                                        isFrozen: false,
                                                                                                                        };

                                                                                                                        const setName = actionCreator<string>("SET_NAME");
                                                                                                                        const addBalance = actionCreator<number>("ADD_BALANCE");
                                                                                                                        const setIsFrozen = actionCreator<boolean>("SET_IS_FROZEN");

                                                                                                                        ...

                                                                                                                        import { reducerWithInitialState } from "typescript-fsa-reducers";

                                                                                                                        const reducer = reducerWithInitialState(INITIAL_STATE)
                                                                                                                        .case(setName, (state, name) => ({ ...state, name }))
                                                                                                                        .case(addBalance, (state, amount) => ({
                                                                                                                        ...state,
                                                                                                                        balance: state.balance + amount,
                                                                                                                        }))
                                                                                                                        .case(setIsFrozen, (state, isFrozen) => ({ ...state, isFrozen }));





                                                                                                                        share|improve this answer




























                                                                                                                          1












                                                                                                                          1








                                                                                                                          1







                                                                                                                          There are libraries that bundle most of the code mentioned in other answers: aikoven/typescript-fsa and dphilipson/typescript-fsa-reducers.



                                                                                                                          With these libraries all your actions and reducers code is statically typed and readable:



                                                                                                                          import actionCreatorFactory from "typescript-fsa";
                                                                                                                          const actionCreator = actionCreatorFactory();

                                                                                                                          interface State {
                                                                                                                          name: string;
                                                                                                                          balance: number;
                                                                                                                          isFrozen: boolean;
                                                                                                                          }

                                                                                                                          const INITIAL_STATE: State = {
                                                                                                                          name: "Untitled",
                                                                                                                          balance: 0,
                                                                                                                          isFrozen: false,
                                                                                                                          };

                                                                                                                          const setName = actionCreator<string>("SET_NAME");
                                                                                                                          const addBalance = actionCreator<number>("ADD_BALANCE");
                                                                                                                          const setIsFrozen = actionCreator<boolean>("SET_IS_FROZEN");

                                                                                                                          ...

                                                                                                                          import { reducerWithInitialState } from "typescript-fsa-reducers";

                                                                                                                          const reducer = reducerWithInitialState(INITIAL_STATE)
                                                                                                                          .case(setName, (state, name) => ({ ...state, name }))
                                                                                                                          .case(addBalance, (state, amount) => ({
                                                                                                                          ...state,
                                                                                                                          balance: state.balance + amount,
                                                                                                                          }))
                                                                                                                          .case(setIsFrozen, (state, isFrozen) => ({ ...state, isFrozen }));





                                                                                                                          share|improve this answer















                                                                                                                          There are libraries that bundle most of the code mentioned in other answers: aikoven/typescript-fsa and dphilipson/typescript-fsa-reducers.



                                                                                                                          With these libraries all your actions and reducers code is statically typed and readable:



                                                                                                                          import actionCreatorFactory from "typescript-fsa";
                                                                                                                          const actionCreator = actionCreatorFactory();

                                                                                                                          interface State {
                                                                                                                          name: string;
                                                                                                                          balance: number;
                                                                                                                          isFrozen: boolean;
                                                                                                                          }

                                                                                                                          const INITIAL_STATE: State = {
                                                                                                                          name: "Untitled",
                                                                                                                          balance: 0,
                                                                                                                          isFrozen: false,
                                                                                                                          };

                                                                                                                          const setName = actionCreator<string>("SET_NAME");
                                                                                                                          const addBalance = actionCreator<number>("ADD_BALANCE");
                                                                                                                          const setIsFrozen = actionCreator<boolean>("SET_IS_FROZEN");

                                                                                                                          ...

                                                                                                                          import { reducerWithInitialState } from "typescript-fsa-reducers";

                                                                                                                          const reducer = reducerWithInitialState(INITIAL_STATE)
                                                                                                                          .case(setName, (state, name) => ({ ...state, name }))
                                                                                                                          .case(addBalance, (state, amount) => ({
                                                                                                                          ...state,
                                                                                                                          balance: state.balance + amount,
                                                                                                                          }))
                                                                                                                          .case(setIsFrozen, (state, isFrozen) => ({ ...state, isFrozen }));






                                                                                                                          share|improve this answer














                                                                                                                          share|improve this answer



                                                                                                                          share|improve this answer








                                                                                                                          edited Oct 10 '18 at 8:50









                                                                                                                          Wish

                                                                                                                          7281828




                                                                                                                          7281828










                                                                                                                          answered Aug 3 '17 at 19:33









                                                                                                                          Max DesiatovMax Desiatov

                                                                                                                          1,65312428




                                                                                                                          1,65312428























                                                                                                                              1














                                                                                                                              Here is how can you do it with redux-fluent:



                                                                                                                              enter image description hereenter image description here






                                                                                                                              share|improve this answer






























                                                                                                                                1














                                                                                                                                Here is how can you do it with redux-fluent:



                                                                                                                                enter image description hereenter image description here






                                                                                                                                share|improve this answer




























                                                                                                                                  1












                                                                                                                                  1








                                                                                                                                  1







                                                                                                                                  Here is how can you do it with redux-fluent:



                                                                                                                                  enter image description hereenter image description here






                                                                                                                                  share|improve this answer















                                                                                                                                  Here is how can you do it with redux-fluent:



                                                                                                                                  enter image description hereenter image description here







                                                                                                                                  share|improve this answer














                                                                                                                                  share|improve this answer



                                                                                                                                  share|improve this answer








                                                                                                                                  edited Mar 11 at 7:56

























                                                                                                                                  answered Mar 11 at 7:20









                                                                                                                                  HitmandsHitmands

                                                                                                                                  6,8781643




                                                                                                                                  6,8781643























                                                                                                                                      -2














                                                                                                                                      Here's the approach I've taken for this problem:



                                                                                                                                      const reducer = (action: IAction) {

                                                                                                                                      const actionA: IActionA = action as IActionA;
                                                                                                                                      const actionB: IActionB = action as IActionB;

                                                                                                                                      switch (action.type) {
                                                                                                                                      case 'a':
                                                                                                                                      // Only ever use actionA in this context
                                                                                                                                      return console.info('action a: ', actionA.a)

                                                                                                                                      case 'b':
                                                                                                                                      // Only ever use actionB in this context
                                                                                                                                      return console.info('action b: ', actionB.b)
                                                                                                                                      }
                                                                                                                                      }


                                                                                                                                      I'll be the first to admit there's a certain ugliness and hackiness to this approach, but I've actually found it to work pretty well in practice. In particular, I find that it makes the code easy to read and maintain because the action's intent is in the name and that also makes it easy to search for.






                                                                                                                                      share|improve this answer




























                                                                                                                                        -2














                                                                                                                                        Here's the approach I've taken for this problem:



                                                                                                                                        const reducer = (action: IAction) {

                                                                                                                                        const actionA: IActionA = action as IActionA;
                                                                                                                                        const actionB: IActionB = action as IActionB;

                                                                                                                                        switch (action.type) {
                                                                                                                                        case 'a':
                                                                                                                                        // Only ever use actionA in this context
                                                                                                                                        return console.info('action a: ', actionA.a)

                                                                                                                                        case 'b':
                                                                                                                                        // Only ever use actionB in this context
                                                                                                                                        return console.info('action b: ', actionB.b)
                                                                                                                                        }
                                                                                                                                        }


                                                                                                                                        I'll be the first to admit there's a certain ugliness and hackiness to this approach, but I've actually found it to work pretty well in practice. In particular, I find that it makes the code easy to read and maintain because the action's intent is in the name and that also makes it easy to search for.






                                                                                                                                        share|improve this answer


























                                                                                                                                          -2












                                                                                                                                          -2








                                                                                                                                          -2







                                                                                                                                          Here's the approach I've taken for this problem:



                                                                                                                                          const reducer = (action: IAction) {

                                                                                                                                          const actionA: IActionA = action as IActionA;
                                                                                                                                          const actionB: IActionB = action as IActionB;

                                                                                                                                          switch (action.type) {
                                                                                                                                          case 'a':
                                                                                                                                          // Only ever use actionA in this context
                                                                                                                                          return console.info('action a: ', actionA.a)

                                                                                                                                          case 'b':
                                                                                                                                          // Only ever use actionB in this context
                                                                                                                                          return console.info('action b: ', actionB.b)
                                                                                                                                          }
                                                                                                                                          }


                                                                                                                                          I'll be the first to admit there's a certain ugliness and hackiness to this approach, but I've actually found it to work pretty well in practice. In particular, I find that it makes the code easy to read and maintain because the action's intent is in the name and that also makes it easy to search for.






                                                                                                                                          share|improve this answer













                                                                                                                                          Here's the approach I've taken for this problem:



                                                                                                                                          const reducer = (action: IAction) {

                                                                                                                                          const actionA: IActionA = action as IActionA;
                                                                                                                                          const actionB: IActionB = action as IActionB;

                                                                                                                                          switch (action.type) {
                                                                                                                                          case 'a':
                                                                                                                                          // Only ever use actionA in this context
                                                                                                                                          return console.info('action a: ', actionA.a)

                                                                                                                                          case 'b':
                                                                                                                                          // Only ever use actionB in this context
                                                                                                                                          return console.info('action b: ', actionB.b)
                                                                                                                                          }
                                                                                                                                          }


                                                                                                                                          I'll be the first to admit there's a certain ugliness and hackiness to this approach, but I've actually found it to work pretty well in practice. In particular, I find that it makes the code easy to read and maintain because the action's intent is in the name and that also makes it easy to search for.







                                                                                                                                          share|improve this answer












                                                                                                                                          share|improve this answer



                                                                                                                                          share|improve this answer










                                                                                                                                          answered Dec 15 '16 at 19:33









                                                                                                                                          Bernard HymmenBernard Hymmen

                                                                                                                                          457412




                                                                                                                                          457412






























                                                                                                                                              draft saved

                                                                                                                                              draft discarded




















































                                                                                                                                              Thanks for contributing an answer to Stack Overflow!


                                                                                                                                              • Please be sure to answer the question. Provide details and share your research!

                                                                                                                                              But avoid



                                                                                                                                              • Asking for help, clarification, or responding to other answers.

                                                                                                                                              • Making statements based on opinion; back them up with references or personal experience.


                                                                                                                                              To learn more, see our tips on writing great answers.




                                                                                                                                              draft saved


                                                                                                                                              draft discarded














                                                                                                                                              StackExchange.ready(
                                                                                                                                              function () {
                                                                                                                                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f35482241%2fhow-to-type-redux-actions-and-redux-reducers-in-typescript%23new-answer', 'question_page');
                                                                                                                                              }
                                                                                                                                              );

                                                                                                                                              Post as a guest















                                                                                                                                              Required, but never shown





















































                                                                                                                                              Required, but never shown














                                                                                                                                              Required, but never shown












                                                                                                                                              Required, but never shown







                                                                                                                                              Required, but never shown

































                                                                                                                                              Required, but never shown














                                                                                                                                              Required, but never shown












                                                                                                                                              Required, but never shown







                                                                                                                                              Required, but never shown







                                                                                                                                              Popular posts from this blog

                                                                                                                                              Biblatex bibliography style without URLs when DOI exists (in Overleaf with Zotero bibliography)

                                                                                                                                              ComboBox Display Member on multiple fields

                                                                                                                                              Is it possible to collect Nectar points via Trainline?