Introduction

I have been working on a few React/ Redux projects recently. Having experience with many statically typed languages, I found it natural to use the Flow type checker or TypeScript. Static types can significantly increase code maintainability - watch Jared Forsyth on React Conf if you are not a believer in static types.

In all projects, I ended up writing very similar type definitions for the Redux actions and reducers. I decided to extract these as reusable generic type definitions.

Fortunately, the syntaxes of Flow and TypeScript are very similar. The following type definitions are valid for both.

Actions

In Redux, an action is an object dispatched from the application code to the redux store. Most actions have a string property called type and a payload property. The following generic type denotes this:

1
2
3
4
export interface Action<Payload> {
  type: string;
  payload: Payload;
}

Many actions represent API calls to a server. In this case, they payload would be a promise that encapsulates the HTTP request. We can use a redux middleware that automatically resolves the payload promises before firing the reducers. Here is a type for this:

1
export type APIAction<Payload> = Action<Promise<Payload>>;

Action Creators

Action creators are functions that return actions. A generic action creator can be defined as:

1
export type ActionCreator<Params, Payload> = (params: Params) => Action<Payload>;

A creator which returns an APIAction:

1
export type APIActionCreator<Params, Payload> = (params: Params) => APIAction<Payload>;

Reducers

A reducer is a function that combines the state and an action to produce the next state. It can be defined as:

1
export type Reducer<State, Payload> = (state: State, action: Action<Payload>) => State;

Example

Let’s look at an example of using the type definitions for an action and a reducer which retrieve a student record from an API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Student is the model type. The redux state is a just list of students
type Student = { id: string, gpa: number };
type State = Student[];

// An API action creator - the app will dispatch the returned action
const getStudent: APIActionCretor<string, Student> = (id) => {
    return {
        type: "GET_STUDENT", 
        // assuming the API call returns Promise<Student>
        payload: callApi(`http://endpoint/${id}`)
    };
}

// Reducer - will merge the state and the resolved promise
const studentReducer: Reducer<State, Student> = (state, action) => {
    switch (action.type) {
        case "GET_STUDENT":
            return [action.payload, ...state];
        default:
            return state
    }
}

NPM Modules

I have published the type definitions to NPM, so they can be used from multiple projects.

If you’re using Flow, then you can install the redux-common-types-flow module:

1
npm install --save redux-common-types-flow

Then you can import the types:

1
2
3
import {type APIActionCreator} from 'redux-common-types-flow';
import {type Action} from 'redux-common-types-flow';
// ....

If TypeScript is your cup of tea, then install redux-common-types-ts:

1
npm install --save redux-common-types-ts

Now you can import as:

1
import { APIActionCreator, Action, /*..*/} from "redux-common-types-ts";