I am creating an app using React and Redux-Observable. I am new to this and I am trying to create an epic to execute the user login.
My epic is below:
export const loginUserEpic = (action$: ActionsObservable<Action>) =>
  action$.pipe(
    ofType<LoginAction>(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))),
      ),
    ),
  );
The problema is that I am getting a Typescript error on this line: ofType<LoginAction>(LoginActionTypes.LOGIN_ACTION) saying this:
Argument of type '(source: Observable<LoginAction>) => Observable<LoginAction>' is not assignable to parameter of type 'OperatorFunction<Action<any>, LoginAction>'.
  Types of parameters 'source' and 'source' are incompatible.
    Type 'Observable<Action<any>>' is not assignable to type 'Observable<LoginAction>'.
      Type 'Action<any>' is not assignable to type 'LoginAction'.
        Property 'payload' is missing in type 'Action<any>'.
My actions are here:
export enum LoginActionTypes {
  LOGIN_ACTION = 'login',
  LOGIN_SUCCESS_ACTION = 'login-sucesss',
  LOGIN_FAILED_ACTION = 'login-failed',
}
export interface LoginAction extends Action {
  type: LoginActionTypes.LOGIN_ACTION;
  payload: {
    username: string;
    password: string;
  };
}
export function login(username: string, password: string): LoginAction {
  return {
    type: LoginActionTypes.LOGIN_ACTION,
    payload: { username, password },
  };
}
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 },
  };
}
export interface LoginFailedAction extends Action {
  type: LoginActionTypes.LOGIN_FAILED_ACTION;
  payload: {
    error: Error;
  };
}
export function loginFailed(error: Error): LoginFailedAction {
  return {
    type: LoginActionTypes.LOGIN_FAILED_ACTION,
    payload: { error },
  };
}
export type LoginActions = LoginAction | LoginSuccessAction | LoginFailedAction;
How can I fix this without using any types on the Epic?
You can checkout https://github.com/piotrwitek/react-redux-typescript-guide for the details below for all the standard way of doing when using React, Redux and Redux-Observable.
I would suggest using  typesafe-actions library to achieve the types.
Some pseudo codes:
Actions
Instead of this
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, without interface
actions/login/LoginActions.ts
import { action } from "typesafe-actions"
export function loginSuccess(loginToken: string) {
  return action(LoginActionTypes.LOGIN_SUCCESS_ACTION, { loginToken });
}
actions/login/LoginActionsModel.ts
import * as LoginActions from "./LoginActions";
import { ActionCreator } from "typesafe-actions";
export type LoginActionCreator = ActionCreator<typeof LoginActions>
Then export out All Actions.
actions/index.ts
import { LoginActionCreator } from "./login/LoginActionModel";
export default type AllActions = LoginActionCreator
Epics
import { Epic } from "redux-observable";
import { isOfType } from "typesafe-actions";
import { filter } from "rxjs/operators";
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);
  })
                        The ofType operator offered by redux-observable is not the best way to discriminate union types. A much better way is to use the isOfType function provided by typesafe-actions.
import { filter } from 'rxjs/operators';
import { isOfType } from 'typesafe-actions';
First, let's tell TypeScript about the possible actions used in your application. Your action stream should not be defined as ActionsObservable<Action>, but as a stream of your actions: ActionsObservable<LoginActions>.
export const loginUserEpic = (action$: ActionsObservable<LoginActions>) =>
Now we can use the isOfType predicate together with the filter operator. Replace this:
ofType<LoginAction>(LoginActionTypes.LOGIN_ACTION)
with this:
filter(isOfType(LoginActionTypes.LOGIN_ACTION))
The action passed down the stream will be correctly recognized as LoginAction.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With