Search code examples
reactjsredux-saga

About the import method of redux saga


I am a 2-month front end developer.

I studied React at the same time as I joined the company, so there are many parts I do not know well.

I am currently analyzing the code to proceed with code maintenance, but there is an esoteric part of the code.

First, In the saga, There is no part where the action function is imported. (even in the root saga.)

So, Is it possible to implicitly import in the code?

I'm attaching some of the code to help you understand.

rootSaga.js

import { all } from "redux-saga/effects";

import watcherLogin from "store/sagas/login";
import watcherSignUp from "store/sagas/signup";

export default function* rootSaga() {
  yield all([
    watcherLogin(),
    watcherSignUp(),
  ]);
}

watcherLogin() > index.js

export { default } from "./watcherLoginSaga"

watcherLogin() > watcherLoginSaga.js

import { all, put, fork, takeLatest } from "redux-saga/effects";
import Cookies from "universal-cookie";
import { fetchData } from "store/sagas/baseSaga";

function* onRequestLogin(action) {
  const payload = action.payload;
  const { history } = payload;
  const successAction = (res) => {

    const cookies = new Cookies();
    cookies.set("hdmKey", res.data, {
      path: "/",
      maxAge: 3600,
    });
    return function* () {

      const payload = res;
      yield put({
        type: "login/GET_USERINFO_REQUEST",
        payload: {
          method: "get",
          api: "getUserInfo",
          // token: res.data.key,
          history,
        },
      });
      yield put({
        type: "login/LOGIN_REQUEST_SUCCESS",
        payload,
      });
      yield put({
        type: "base/IS_LOGGED",
        payload,
      });
      yield history.push("/");
    };
  };
  const failureAction = (res) => {

    return function* () {

      yield put({
        type: "base/SHOW_MODAL",
        payload: {
          dark: true,
          modalName: "alert",
          modalContent: "login failure",
          modalStyle: "purpleGradientStyle",
          modalTitle: "Wait!",
        },
      });
    };
  };
  yield fork(fetchData, payload, successAction, failureAction);
}
...

export default function* watcherLoginSaga() {
  yield all([
    takeLatest("login/LOGIN_REQUEST", onRequestLogin),
  ]);
}

loginModule > index.js

export { default } from "./loginModule";

loginModule > loginModule.js

import createReducer from "store/createReducer";
import { changeStateDeep } from "lib/commonFunction";

export const types = {
  LOGIN_REQUEST: "login/LOGIN_REQUEST",
  LOGIN_REQUEST_SUCCESS: "login/LOGIN_REQUEST_SUCCESS",
  ...
};
export const actions = {
  loginRequest: (payload) => ({
    type: types.LOGIN_REQUEST,
    payload,
  }),
...
};
const INITIAL_STATE = {
  data: {
    isLogged: false,
    ...
  },
};
const reducer = createReducer(INITIAL_STATE, {

  [types.ON_LOGIN]: (state, action) => {
    state.data.isLogged = true;
  },
  [types.LOGIN_REQUEST_SUCCESS]: (state, action) => {
    state.data.isLogged = true;
    state.data.key = action.payload?.key || "key";
  },
...
});
export default reducer;

I would appreciate it if you could help even a little.


Solution

  • An action is just a plain javascript object with a property type. As a convention (but not a strict requirement), action objects store their data in an optional property payload.

    An actionCreator is a function which takes 0 to many arguments and uses them to create an action object.

    What you are seeing in the code that you posted works, but it's not ideal. I'm guessing that certain improvements were made to the loginModule.js file and those improvements were not brought over to the watcherLoginSaga.js file.

    Your types object allows you to use a variable such as types.LOGIN_REQUEST rather than the raw string "login/LOGIN_REQUEST". This has a bunch of benefits:

    • you get intellisense support in your IDE for autocomplete, etc.
    • you don't have to worry about making typos
    • you can change the value of the underlying raw string and you just need to change it one place

    That last one is critical, because if you were to change the value of types.LOGIN_REQUEST to anything other than "login/LOGIN_REQUEST" right now your sagas would stop working because they are using the raw string. So you are absolutely right in thinking that the saga should import from the actions. I recommend that you import your types and replace the strings with their corresponding variables.

    The same situation is happening with the actions that the saga is dispatching via put. What's going on in this code is that the saga is creating a raw action object itself from the type and payload rather than creating it though an action creator function. That's fine but it's not great. Like the types, action creators are a level of abstraction that allows for better code maintenance. You could definitely extract the logic from your saga into action creator functions, if they don't exist already.

    For example, your base module could include:

    const types = {
      IS_LOGGED: "base/IS_LOGGED",
      SHOW_MODAL: "base/SHOW_MODAL"
    };
    
    export const actions = {
      isLogged: (payload) => ({
        type: types.IS_LOGGED,
        payload
      }),
      showLoginFailure: () => ({
        type: types.SHOW_MODAL,
        payload: {
          dark: true,
          modalName: "alert",
          modalContent: "login failure",
          modalStyle: "purpleGradientStyle",
          modalTitle: "Wait!"
        }
      })
    };
    

    And you can move the logic for creating actions away from your saga.

    import { all, put, fork, takeLatest } from "redux-saga/effects";
    import Cookies from "universal-cookie";
    import { fetchData } from "store/sagas/baseSaga";
    import {actions as loginActions, types as loginTypes} from "../some_path/loginModule";
    import {actions as baseActions} from "../some_path/baseModule";
    
    function* onRequestLogin(action) {
      const payload = action.payload;
      const { history } = payload;
      const successAction = (res) => {
    
        const cookies = new Cookies();
        cookies.set("hdmKey", res.data, {
          path: "/",
          maxAge: 3600,
        });
        return function* () {
    
          const payload = res;
          yield put(loginActions.getUserInfoSuccess(history));
          yield put(loginActions.loginSuccess(payload));
          yield put(baseActions.isLogged(payload));
          yield history.push("/");
        };
      };
      const failureAction = (res) => {
    
        return function* () {
    
          yield put(baseActions.showLoginFailure());
        };
      };
      yield fork(fetchData, payload, successAction, failureAction);
    }
    
    export default function* watcherLoginSaga() {
      yield all([
        takeLatest(loginTypes.LOGIN_REQUEST, onRequestLogin),
      ]);
    }