typescriptreact-nativeredux-toolkitredux-observable

Epics not triggering in Redux observable with Redux toolkit


I tried to follow the documentation and this is what I did

middleware/index.ts

import { combineEpics } from "redux-observable";
import userEpic from "./userEpic";

export const rootEpic = combineEpics(
    userEpic,
);

store.ts

import { configureStore } from "@reduxjs/toolkit";
import { createEpicMiddleware } from "redux-observable";
import { rootEpic } from "../middleware";
import userReducer from "./reducers/userSlice";

const rootReducer = {
  user: userReducer,
};
const epicMiddleware = createEpicMiddleware();

export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
  getDefaultMiddleware().concat(epicMiddleware),
});

epicMiddleware.run(rootEpic);

export type RootStateType = ReturnType<typeof store.getState>;
export type AppDispatchType = typeof store.dispatch;

export default store;

actions.ts

export const LOGIN_USER = "LOGIN_USER";
export const loginUser = () => {
  return {
    type: LOGIN_USER,
  } as const;
};
export type LoginUserAction = ReturnType<typeof loginUser>;

userEpic.ts (this is where the API call should be but I made this way just to test if it is working)

import { Action } from "@reduxjs/toolkit";
import { Observable } from "rxjs";
import { combineEpics, ofType } from "redux-observable";
import { LOGIN_USER } from "./actions";
import { map } from "rxjs/operators";
import { setUserData } from "../../redux/reducers/userSlice";

const fetchUserEpic = (action$: Observable<Action>) => {
  return action$.pipe(
    ofType(LOGIN_USER),
    map(() =>
      setUserData({
        id: 12,
        name: "test",
        surname: "test",
        username: "test",
        refreshToken: "test",
        accessToken: "test",
      })
    )
  );
};

export default combineEpics(fetchUserEpic);

userSlice.ts

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { FetchStatus } from "../../modules/types";
import { RootStateType } from "../store";

type UserType = {
  id: number;
  name: string;
  surname: string;
  username: string;
  refreshToken: string;
  accessToken: string;
};

type UserStateType = {
  user: UserType | null;
  userFetchStatus: FetchStatus;
};

const initialUserState: UserStateType = {
  user: null,
  userFetchStatus: FetchStatus.success,
};

export const userSlice = createSlice({
  name: "user",
  initialState: initialUserState,
  reducers: {
    setUserData: (state, action: PayloadAction<UserType>) => {
      state.user = action.payload;
      state.userFetchStatus = FetchStatus.success;
    },
    setUserFetchingStatus: (state, action: PayloadAction<FetchStatus>) => {
      state.userFetchStatus = action.payload;
    },
    clearUserData: () => {
      return initialUserState;
    },
  },
  extraReducers: (builder) => {
    builder.addDefaultCase((state) => {
      return state;
    });
  },
});

export const { setUserData, clearUserData } = userSlice.actions;

export const getUserData = (state: RootStateType) => state.user;

export default userSlice.reducer;

And I have some component where I trigger loginUser and then try to console log it from the redux

component.ts

const dispatch = useAppDispatch()
const user = useAppSelector(getUserData);
console.log(user)
<TouchableOpacity onPress={() => dispatch(loginUser)}>
//...

package.json

  "react-redux": "^8.0.1",
  "redux": "^4.2.0",
  "redux-observable": "^2.0.0"

Looks like the redux store is not changing, loginUser action is triggering but looks like ofType(LOGIN_USER) is not catching it, I saw in some other thread and documentation to use filter(actionFunction.match), instead of ofType, but I think that is only for redux actions? And when I use the filter I get an error that is deprecated.


Solution

  • You have to call your action creator:

    dispatch(loginUser()), not dispatch(loginUser).

    Apart from that, you should definitely not be writing that action creator by hand - if you don't want to make it with createSlice, at least use createAction for it.

    export const loginUser = createAction('user/login').

    Then you can also use filter(loginUser.match) with it.