Quick simple guide on making Redux-Observable works with TypeScript

This is a guide I written on stack overflow, which I plan to rewrite it into a simple post.

You can checkout React Redux Typescript Guide for the details in here for all the standard way of doing when using React, Redux and Redux-Observable.

Introduction

Image Credit

This guide I would like to talk a bit about using Redux-Observable alongside with TypeScript and make the types work out of the box.

I will be using typesafe-actions library to achieve the types. (GitHub for typesafe-actions)

Actions

Instead of declaring interface (types) expressively, (something like below)

export interface LoginSuccessAction extends Action {  type: LoginActionTypes.LOGIN_SUCCESS_ACTION;  payload: {    loginToken: string;  };}export function loginSuccess(loginToken: string): LoginSuccessAction {  return {    type: LoginActionTypes.LOGIN_SUCCESS_ACTION,    payload: { loginToken },  };}

use typesafe-actions library without declaring any interface.

actions/login/LoginActions.ts

import {action} from "typesafe-actions"export function loginSuccess(loginToken: string) {  return action(LoginActionTypes.LOGIN_SUCCESS_ACTION, { loginToken });}

Then, all the login actions are being exported out in the model file.

actions/login/LoginActionsModel.ts

import * LoginActions from "./LoginActions";import { ActionCreator } from "typesafe-actions";export type LoginActionCreator = ActionCreator<typeof LoginActions>

Then export out all the actions as AllActions.

actions/index.ts

import { LoginActionCreator } from "./login/LoginActionModel";export default type AllActions = LoginActionCreator

Epics

In the epics, import following library.

import { Epic } from "redux-observable";import { isOfType } from "typesafe-actions";import { filter } from "rxjs/operators";

Then simply use Epic to declare your types.

export const loginUserEpic: Epic<AllActions> = (action$) =>  action$.pipe(    filter(isOfType((LoginActionTypes.LOGIN_ACTION))),    switchMap((action: LoginAction) =>      ajax({        url,        method: 'POST',        headers: { 'Content-Type': 'application/json' },        body: { email: action.payload.username, password: action.payload.password },      }).pipe(        map((response: AjaxResponse) => loginSuccess(response.response.token)),        catchError((error: Error) => of(loginFailed(error))),      ),    ),  );

Where the Epics is from redux-observable library, AllActions are the actions that is input and output of the epics.

The types is as follows:

Epic<InputActions, OutputActions, Store>Epic<Actions(Input&Output)>

In case you want to use store from redux, you need a RootState (from root reducer)

export const someEpic: Epic<AllActions, AllActions, RootState> = (action$, store$) => action$.pipe(  filter(isOfType(SOMETYPE)),  mergeMap(action => {    const a = store$.value.a;    return someActions(a, action.payload.b);  })