Search code examples
reactjsredux-saga

React redux Sagas, wait for a Saga to set state


I have some component with a code like this:

const startLogin = (code) => {
  dispatch(login({ code }));
  const publicKeyFromLocalST = window.localStorage.getItem('push_public_key');
  setPublicKey(publicKeyFromLocalST);
  // etc

When I dispatch the saga login it will store some data in localStorage. I need to execute the 3rd line (setPublicKey) after that data be actually indeed in localStorage.

How can "await" for dispatch(login({ code })); to be completed before setPublicKey?


Solution

  • Two options:

    1. Execute the setPublicKey function inside the worker saga, you can control the workflow in the worker saga easily with yield.
    function* login(action) {
      const response = yield call(apiCall);
      if (response.error) {
        yield put({ type: actionType.LOGIN_FAIL });
      } else {
        yield put({ type: actionType.LOGIN_SUCCESS, data: response.data });
        const publicKeyFromLocalST = window.localStorage.getItem('push_public_key');
        setPublicKey(publicKeyFromLocalST);
      }
    }
    
    1. Promisify the dispatch(login({code})), you should create a helper function like this:
    const loginAsyncCreator = (dispatch) => (payload) => {
      return new Promise((resolve, reject) => dispatch(loginCreator(payload, { resolve, reject })));
    };
    

    You need to pass the resolve/reject to worker saga via action.meta, then you can decide when to resolve or reject the promise. Then, you can use async/await in your event handler. See below example:

    import { call, put, takeLatest } from 'redux-saga/effects';
    import { createStoreWithSaga } from '../../utils';
    const actionType = {
      LOGIN: 'LOGIN',
      LOGIN_FAIL: 'LOGIN_FAIL',
      LOGIN_SUCCESS: 'LOGIN_SUCCESS',
    };
    function apiCall() {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve({ error: null, data: 'login success' });
        }, 2000);
      });
    }
    
    function* login(action) {
      console.log('action: ', action);
      const {
        meta: { resolve, reject },
      } = action;
      const response = yield call(apiCall);
      console.log('response: ', response);
      if (response.error) {
        yield put({ type: actionType.LOGIN_FAIL });
        yield call(reject, response.error);
      } else {
        yield put({ type: actionType.LOGIN_SUCCESS, data: response.data });
        yield call(resolve, response.data);
      }
    }
    
    function* watchLogin() {
      yield takeLatest(actionType.LOGIN, login);
    }
    
    const store = createStoreWithSaga(watchLogin);
    
    function loginCreator(payload, meta) {
      return {
        type: actionType.LOGIN,
        payload,
        meta,
      };
    }
    
    const loginAsyncCreator = (dispatch) => (payload) => {
      return new Promise((resolve, reject) => dispatch(loginCreator(payload, { resolve, reject })));
    };
    
    const loginAsync = loginAsyncCreator(store.dispatch);
    
    async function startLogin() {
      await loginAsync({ code: '1' });
      console.log('setPublicKey');
    }
    
    startLogin();
    

    The logs:

    action:  {
      type: 'LOGIN',
      payload: { code: '1' },
      meta: { resolve: [Function (anonymous)], reject: [Function (anonymous)] }
    }
    response:  { error: null, data: 'login success' }
    setPublicKey