Reputation: 159
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
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