node package manager
It’s your turn. Help us improve JavaScript. Take the 2017 JavaScript Ecosystem Survey »

redux-typed-actions

Redux Typed Actions

An opinionated approach to type actions and their payload in Redux with statically type checking in Typescript. This approach removes the most possible boilerplate of your actions & their creators in React and any other frameworks.

Installation

Let's get started by installing it from npm repository

$ npm install --save-dev redux-typed-actions

or from yarn repository

$ yarn add --dev redux-typed-actions

Usage

Let's do a quick example to see how this approach can improve type checking of redux actions

  1. You have an ItemX interface:
    feature-x.types.ts

    export interface ItemX {
      title: string;
    }
  2. First we define our actions:
    feature-x.actions.ts

    import { defineAction, defineScenarioAction, defineSymbolAction } from 'redux-typed-actions';
    import { ItemX } from './feature-x.types';
     
    // For this action we will have number as our payload
    export const FeatureXAddTicketAction = defineAction<number>('[Feature X] Add Ticket');
     
    /**
     *  This action is special, it's called a scenario-like action
     *  It notifies the system with status of a process covering from start to end.
     *  You can get Start/Success/Failure/Cancel from this action generator/creator
     *  There are 4 types that belong respectively to Start/Success/Failure/Cancel
     *  Note: The default type for payload of success and failure is
     *  string so you can skip them like `defineScenarioAction('MyActionName')`
     */
    export const FeatureXLoadAction = defineScenarioAction<never, ItemX[], string>('[Feature X] Load');
     
    // Let's have a symbol action just for fun
    export const FeatureXDummySymbolAction = defineSymbolAction<ItemX[]>('[Feature X] Dummy Started');

    Note: You can setup your own suffix for Start/Success/Failure/Cancel of scenario actions as following example:

    import { factory } from 'redux-typed-actions';
     
    // You must set them before defining actions
    factory.setSuffixes({
      start: '_REQUEST',
      cancel: '_CANCEL',
      success: '_SUCCESS',
      failure: '_FAILURE',
    });
  3. Now we dispatch our actions:
    feature-x.component.ts

    import { ItemX } from './feature-x.types';
    import { FeatureXAddTicketAction, FeatureXLoadAction } from '../feature-x.actions';
    ...
     
    // React Redux solution to replace action creators:
    // Let's define our component's state
    interface FeatureXProps {
      ...
      addTicket: typeof FeatureXAddTicketAction.strictGet; // StrictGet makes the payload mandatory
    }
     
    ...
    class FeatureX extends React.Component<FeatureXProps> {
      ...
      addTicket = () => this.props.addTicket(1) // <- Static type checking
     
      render() {
        return (<button onClick={this.addTicket}>Add one ticket</button>); // />
      }
    }
     
    // Let's hook the action to redux, and we're done
    export default connect(undefined, { addTicket: FeatureXAddTicketAction.strictGet })(FeatureX);
     
     
    // All Typescript frameworks:
    // Dispatching a simple action
    store.dispatch(FeatureXAddTicketAction.get(100));
     
    // Let's start our scenario by dispatching our action
    store.dispatch(FeatureXLoadAction.get());
     
    // Now we dispatch the success action
    const payload: ItemX[] = [{ title: 'item 1' }, { title: 'item 2' }];
    dispatch(FeatureXLoadAction.success.get(payload));
     
    // or simply a failure
    dispatch(FeatureXLoadAction.failure.get('It failed because...'));
  4. Now let's take a look at our reducers to see how we can type check the payloads:
    feature-x.reducers.ts

    import { PlainAction } from 'redux-typed-actions';
    import { ItemX } from './feature-x.types';
    import { FeatureXLoadAction } from '../feature-x.actions';
     
    export interface ItemXState {
      items: ItemX[];
      loading: boolean;
    }
     
    export function reducer(state: ItemXState = InitialState, action: PlainAction): ItemXState {
      if (FeatureXLoadAction.is(action)) {
        // Within this branch our action variable has the right typings
        return {
          ...state,
          loading: true,
        };
     
      } else if (FeatureXLoadAction.success.is(action)) {
        return {
          ...state,
          loading: false,
          items: action.payload, // <- Here we are checking types strongly
        };
     
      } else {
        return state;
      }
    }
  5. If you're fan of redux-observables then this part is for you:
    feature-x.epics.ts

    (action$, store) => action$
       .ofType(FeatureXLoadAction.type)
       .map(action => FeatureXLoadAction.cast(action)) // <- from here we will have all the typings right :)
       .switchMap(action => Service()
           .map(value => FeatureXLoadAction.success.get(repos))
           .catch(() => Observable.of(FeatureXLoadAction.failure.get('Oops something went wrong!'))));
  6. That's it

Take a look at React or Angular examples for a closer look.