How to achieve callbacks in Redux-Saga?
Asked Answered
V

5

26

The scenario is, I want to redirect a user or show alert based on the success, error callbacks after dispatching an action.

Below is the code using redux-thunk for the task

this.props.actions.login(credentials)
.then((success)=>redirectToHomePage)
.catch((error)=>alertError);

because the dispatch action in redux-thunk returns a Promise, It is easy to act with the response.

But now I'm getting my hands dirty on redux-saga, and trying to figure out how I can achieve the same result as above code. since Saga's run on a different thread, there is no way I can get the callback from the query above. so I just wanted to know how you guys do it. or whats the best way to deal with callbacks while using redux-saga ? the dispatch action looks like this :

this.props.actions.login(credentials);

and the saga

function* login(action) {
  try {
    const state = yield select();
    const token = state.authReducer.token;
    const response = yield call(API.login,action.params,token);
    yield put({type: ACTION_TYPES.LOGIN_SUCCESS, payload:response.data});
    yield call(setItem,AUTH_STORAGE_KEY,response.data.api_token);
  } catch (error) {
    yield put({type: ACTION_TYPES.LOGIN_FAILURE, error})
  }
}

saga monitor

export function* loginMonitor() {
  yield takeLatest(ACTION_TYPES.LOGIN_REQUEST,login);
}

action creator

function login(params) {
  return {
    type: ACTION_TYPES.LOGIN_REQUEST,
    params
  }
}
Vaulted answered 10/12, 2016 at 14:24 Comment(3)
redirectToHomePage can you please show me this method code because my app is not navigation to home pageZionism
See https://mcmap.net/q/536871/-promises-in-redux-saga for my answer to a similar question, arranging for dispatch() to return a promise in redux-saga.Ambassadress
> But now I'm getting my hands dirty on redux-saga the same feeling with the same problem brought me here :)Indraft
Q
13

I think you should add redirect and alert to the login generator. This way all logic is in the saga and it is still easily tested. So basically your login saga would look like this:

function* login(action) {
  try {
    const state = yield select();
    const token = state.authReducer.token;
    const response = yield call(API.login,action.params,token);
    yield put({type: ACTION_TYPES.LOGIN_SUCCESS, payload:response.data});
    yield call(setItem,AUTH_STORAGE_KEY,response.data.api_token);
    yield call(redirectToHomePage); // add this...
  } catch (error) {
    yield put({type: ACTION_TYPES.LOGIN_FAILURE, error});
    yield call(alertError); // and this
  }
}
Quinones answered 11/12, 2016 at 18:35 Comment(3)
Yes, this is what I ended up doing. Glad that we both opted out for same solution. ThanksVaulted
Good to hear! Also if you want to accept the answer so other people can find it easily.Quinones
I'm using the same approach, but our app is huge and i'm facing some limitations... Depending on the component doing to action, different navigation should happen. At first I added navigateTo as a parameter to the action, but this does not work with e.g. NavigationActions.back(). Then the solution would be to add a callback to the action (you see where this is going...?). But of course callbacks should be replaced by promises... async-await... generators... saga's?Boodle
S
20

I spent all day dinking around with this stuff, switching from thunk to redux-saga

I too have a lot of code that looks like this

this.props.actions.login(credentials)
.then((success)=>redirectToHomePage)
.catch((error)=>alertError);

its possible to use thunk + saga

function login(params) {
  return (dispatch) => {
    return new Promise((resolve, reject) => {
      dispatch({
        type: ACTION_TYPES.LOGIN_REQUEST,
        params,
        resolve, 
        reject
      })
    }
  }
}

then over in saga land you can just do something like

function* login(action) {
  let response = yourApi.request('http://www.urthing.com/login')
  if (response.success) {
    action.resolve(response.success) // or whatever
  } else { action.reject() }
}
Sammy answered 3/3, 2017 at 23:52 Comment(0)
Q
13

I think you should add redirect and alert to the login generator. This way all logic is in the saga and it is still easily tested. So basically your login saga would look like this:

function* login(action) {
  try {
    const state = yield select();
    const token = state.authReducer.token;
    const response = yield call(API.login,action.params,token);
    yield put({type: ACTION_TYPES.LOGIN_SUCCESS, payload:response.data});
    yield call(setItem,AUTH_STORAGE_KEY,response.data.api_token);
    yield call(redirectToHomePage); // add this...
  } catch (error) {
    yield put({type: ACTION_TYPES.LOGIN_FAILURE, error});
    yield call(alertError); // and this
  }
}
Quinones answered 11/12, 2016 at 18:35 Comment(3)
Yes, this is what I ended up doing. Glad that we both opted out for same solution. ThanksVaulted
Good to hear! Also if you want to accept the answer so other people can find it easily.Quinones
I'm using the same approach, but our app is huge and i'm facing some limitations... Depending on the component doing to action, different navigation should happen. At first I added navigateTo as a parameter to the action, but this does not work with e.g. NavigationActions.back(). Then the solution would be to add a callback to the action (you see where this is going...?). But of course callbacks should be replaced by promises... async-await... generators... saga's?Boodle
R
1

You can simply work up by passing the extra info about your success and error callback functions into the payload itself. Since, redux pattern works in a quite decoupled manner.

this.props.actions.login({
   credentials,
   successCb: success => redirectToHomePage)
   errorCb: error => alertError)
 });

In the saga, you can deconstruct these callbacks from the payload and run them very easily based on your program flow.

Rey answered 28/8, 2019 at 2:14 Comment(0)
S
1

Your call:

this.props.addCutCallback(currentTime, callback);

Your mapping that you pass to connect() function:

const mapDispatchToProps = (dispatch) => ({
  addCutCallback: (time, callback) =>
    dispatch(ACTIONS.addCutCallback(time, callback)),
});

export default connect(mapStateToProps, mapDispatchToProps)(Home);

Your saga:

import {put, takeEvery, all, select} from 'redux-saga/effects';
import * as Actions from './../actions';

const getCuts = (state) => state.cuts;

function* addCutSaga({time, callback}) {
  yield put({type: Actions.ADD_CUT, time});
  const cuts = yield select(getCuts);
  callback(cuts);
}

function* cutsSaga() {
  yield takeEvery(Actions.ADD_CUT_CALLBACK, addCutSaga);
}

export default function* rootSaga() {
  yield all([cutsSaga()]);
}
Soidisant answered 7/7, 2020 at 20:51 Comment(0)
R
0

An approach that I find more elegant is to simply use the useEffect.

//selectors.ts

export function hasAuthError(state: RootState): boolean {
  return state.auth.error;
}

export function getAuthMessage(state: RootState): string {
  return state.auth.message;
}
// some react component

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Toast from 'react-native-toast-message';
import { getAuthMessage, hasAuthError } from 'src/store/auth/selectors';

  ...

  const message = useSelector(getAuthMessage);
  const hasError = useSelector(hasAuthError)

  ...

  useEffect(() => {
    if (hasError) {
      Toast.show({
        type: 'error',
        text2: message,
        topOffset: 50,
        visibilityTime: 5000
      });
    }
  }, [message, hasError]);
Raceway answered 3/6, 2021 at 20:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.