Saiful Islam
Saiful Islam

Reputation: 159

Redux-saga: return promises from redux actions

I am using redux-saga. I want to dispatch an action and use the result of the action immediately after. But the saga dispatches asynchronously and the result is not available right after.

class A extends Component{
    handleEvent = (e) =>{
       this.props.getInfo(param);
       //Use Result(this.props.Info) from the action
    }
    render(){
    }
}
function mapStateToProps(state) {
    return {
        Info : state.infoReducer.info
    };
}
function mapDispatchToProps(dispatch) {
    return {
        getInfo : (val)=>{dispatch(getInfo(val))}          
    }
}
export default connect(mapStateToProps, mapDispatchToProps) (A); 

My action creator:

export function getInfo(data){
    return{
        type : actionType.GET_INFO,
        payload : data
    }
}

The Saga function:


function* getInfo(action){
    try {
     const response = yield call(post,Endpoint,param,header);
        const data =  yield response.data;
        if(error) yield put({type: GET_INFO_FAIL});
        else yield put({ type:  GET_INFO_SUCCESS, data });
        
    } catch (e) {
        //
    } 
}

The Reducer:

const INITIAL_STATE ={
    info : ""
}
const infoReducer=(state = INITIAL_STATE, action) => {
    switch (action.type) {
        case actionType.GET_INFO_SUCCESS:
        return{
            info : action.data
        }
        default:
            return state;
    }

}

Upvotes: 1

Views: 1962

Answers (1)

Lin Du
Lin Du

Reputation: 102237

You can create an action creator with promise. Pass the resolve and reject functions as meta to the action creator. Then call resolve and reject in worker saga after the API invoking finished.

We can create bindActionToPromise helper function to do this.

const bindActionToPromise = (dispatch, actionCreator) => (payload) => {
  return new Promise((resolve, reject) => dispatch(actionCreator(payload, { resolve, reject })));
};

function mapDispatchToProps(dispatch) {
    return {
        getInfo: bindActionToPromise(dispatch, getInfo)
    }
}

getInfo worker saga:

function* getInfoSaga(action) {
  console.log(action);
  const {
    meta: { resolve, reject },
  } = action;
  const response = yield call(apiCall);
  if (response.error) {
    yield put({ type: actionType.GET_INFO_FAIL });
    yield call(reject, response.error);
  } else {
    yield put({ type: actionType.GET_INFO_SUCCESS, data: response.data });
    yield call(resolve, response.data);
  }
}

Then, you can get the API response immediately like this:

 this.props.getInfo(param).then(res => console.log(res))

An complete, working example:

import { call, put, takeLatest } from 'redux-saga/effects';
import { createStoreWithSaga } from '../../utils';

const actionType = { GET_INFO: 'GET_INFO', GET_INFO_FAIL: 'GET_INFO_FAIL', GET_INFO_SUCCESS: 'GET_INFO_SUCCESS' };

export function getInfo(payload, meta) {
  return {
    type: actionType.GET_INFO,
    payload,
    meta,
  };
}

function apiCall() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ data: 'Info from API', error: null });
      //   resolve({ data: null, error: new Error('business error') });
    }, 1000);
  });
}

function* getInfoSaga(action) {
  console.log(action);
  const {
    meta: { resolve, reject },
  } = action;
  const response = yield call(apiCall);
  if (response.error) {
    yield put({ type: actionType.GET_INFO_FAIL });
    yield call(reject, response.error);
  } else {
    yield put({ type: actionType.GET_INFO_SUCCESS, data: response.data });
    yield call(resolve, response.data);
  }
}

function* watchSaga() {
  yield takeLatest(actionType.GET_INFO, getInfoSaga);
}

const INITIAL_STATE = {
  info: '',
};
const infoReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case actionType.GET_INFO_SUCCESS:
      return {
        info: action.data,
      };
    default:
      return state;
  }
};

const store = createStoreWithSaga(watchSaga, infoReducer);

export const bindActionToPromise = (dispatch, actionCreator) => (payload) => {
  return new Promise((resolve, reject) => dispatch(actionCreator(payload, { resolve, reject })));
};

const boundGetInfo = bindActionToPromise(store.dispatch, getInfo);
boundGetInfo({ id: 1 })
  .then((res) => {
    console.log('res immediately: ', res);
  })
  .catch((err) => {
    console.log('error immediately: ', err);
  });
store.subscribe(() => {
  console.log('state: ', store.getState());
});

Execution output:

{
  type: 'GET_INFO',
  payload: { id: 1 },
  meta: { resolve: [Function (anonymous)], reject: [Function (anonymous)] }
}
state:  { info: 'Info from API' }
res immediately:  Info from API

Upvotes: 2

Related Questions