For a long while I’ve been trying to figure out how to secure redux within typescript, how to type every single parameter and make sure nothing resolves to the any type.

In short, my conclusion is; Redux wasn’t made to be typed, you define reducers which take an action argument that could literally be anything, any action in the whole system with any parameters, this makes things complicated for sure. But in sacrifice of some simplicity, I’ve managed to come up with a solution that works quite well.

The goal…

…is to secure as many connections between different parts of the app as possible. To make sure you can’t use wrong props, wrong arguments and so on. But also to get proper type hinting when calling actions, when using payload in the reducer, when mapping data from the store, and so on.

To implement this idea, you’re required to have a redux-project setup or at least have basic knowledge of how a redux implementation works. The parts that need to be attended are:

Store configuration

Root reducer

Writing actions

Writing reducers

Writing selectors

Connecting store to components

Let’s start!

I’ll be going through the components one by one, to start off I’ll use a couple of example interfaces throughout the tutorial, if you want to use them they need to be imported in every file they are used.

// The model interface used in the examples

interface Car {

id: number;

name: string;

color: string;

} // An action interface, forcing all actions to implement "type"

interface Action {

readonly type: string;

} // A thunk action interface, easing use of thunks

interface ThunkAction {

dispatch: (action: Action | ThunkAction) => void,

getState: () => ReduxState

fetch: (input: RequestInfo, init?: RequestInit): Promise<Response>;

}

These are the version I’ve been using

react: 16.3.0

react-redux: 5.0.6

redux: 3.7.2

redux-plainify: 0.3.1

redux-thunk: 2.2.0

reselect: 3.0.1

Store configuration

The store needs to be setup in normal order, except one middleware needs to be added, I’ve created a small npm package called redux-plainify to support this article, this needs to be included because the actions differ from normal redux actions. The redux package doesn’t accept actions with another constructor than “Object”, it will throw an error, hence we “plainify” the actions by mapping the values into a new object.

import { createStore, applyMiddleware } from 'redux'

import thunk from 'redux-thunk'

import plainify from 'redux-plainify'

import rootReducer from './root-reducer' const store = createStore(rootReducer, applyMiddleware([

rootReducer,

thunk.withExtraArgument(fetch),

plainify

]);

Root Reducer

The root reducer looks like a normal root reducer, except you apply a global state which you can import wherever you’d like. It’s important to export the State as well as the actual reducer in every reducer file.

import car, { State as CarState } from './car/reducer' export interface ReduxState {

car: CarState;

} const rootReducer = combineReducers({

car

}); export default rootReducer;

Writing actions

To accomplish strict typed actions, I’ve made them into classes, every single action is it’s own class. This way you don’t have to define an interface along side your action creators, it’s simply a class.

There are different ways of handling action types, in this article I will deploy them along side actions, but the key idea in this concept is to make them into enums. If they are enums, they are easily identifiable with TypeScript.

export enum ActionType {

fetch: '[Cars] Fetch',

create: '[Cars] Create',

}

Now that we’ve created our action types, let’s create an action. An action will have a public readonly property called type. The readonly attribute makes sure you can’t change the property, thus TypeScript will be able to resolve the type as the specific enum, so we can connect the payload to an action type. We’ll cover this further in the reducer example.

export class FetchAction implements Action {

public readonly type = ActionTypes.fetch;

constructor(public cars: Array<Car>) {}

} export class CreateAction implements Action {

public readonly type = ActionTypes.create;

constructor(public car: Car, public payload: string) {}

}

Since thunk actions are so common, I’ll provide an example on how to create a type thunk action as well. If the ThunkAction interface described in the prelude is implemented, we don’t have to type the dispatch, getState or any other arguments in the thunk function.

export function createCar(car: Car): ThunkAction {

return async (dispatch, getState, fetch) => {

const response = await fetch('/cars');

dispatch(new CreateAction(response));

}

}

Writing reducers

When it comes to reducers, the first thing we need to acknowledge is that we subscribe to the actions we want the reducers to listen to. Once we’ve defined the actions we listen to, we can get proper typing in our switch case.

Since we’ve connected action types to classes with public properties, TypeScript is smart enough to nail down which properties belong to which action type, hence we can only use the “cars” property within the fetch action type case.

NOTE: You can use “payload” as an argument in the actions, and TypeScript will nail down the exact type it has, depending on the action type as well.

import { ActionType, FetchAction, CreateAction } from './actions'; export interface State {

cars: Array<Car>

} type Action = FetchAction | CreateAction const initialState = {

cars: []

}; const reducer = function(state = initialState, action: Action): State {

switch (action.type) {

case ActionType.Fetch:

return { ...state, action.cars.slice() };

case ActionType.Create:

return { ...state, cars: [...state.cars, action.car] };

default:

return state;

}

}

Writing selectors

Selectors are crucial in a redux app, you can modify behavior in views without altering the store. There are many ways of writing selectors, this is one example with this setup.

import ReduxStore from 'store' export const getRedCars = createSelector(

(state: ReduxStore) => state.carNode.cars,

(cars: Array<Car>) => {

return cars.filter(car => car.color === 'red');

}

);

Connecting store to components

The last step is to map actions and store nodes to your components, this pretty much looks like normal, but when using classes, you obviously have to instantiate them and provide the arguments in the constructor.

import ReduxStore from 'store' const mapStateToProps = (state: ReduxStore) => ({

allCars: state.carNode.cars,

redCars: getRedCars(state)

}); const mapDispatchToProps = dispatch => ({

fetch: () => dispatch(new FetchAction()),

create: (car: Car) => dispatch(new CreateAction(car))

}); export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

Conclusion

There are more steps to take, more ground to cover when it comes to typing React and Redux, I have ideas where I could improve the typing, but at least this brings a foundation how to type Redux without modifying the integrity of how Redux is used.

I hope this helped someone, feedback is appreciated :-)