How to achieve callbacks in Redux-Saga?

ghz 6months ago ⋅ 76 views

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
  }
}

Answers

In Redux Saga, handling callbacks after dispatching an action involves a different approach compared to Redux Thunk's promise-based approach. Redux Saga uses generators to manage asynchronous operations and provides ways to handle success and error scenarios within the Saga itself. Here's how you can achieve similar behavior to your Redux Thunk example:

Handling Callbacks in Redux Saga

Given your setup with Redux Saga, here's how you can modify it to handle success and error callbacks:

  1. Dispatching the Action:

    You dispatch the action as usual from your component:

    this.props.dispatch(loginAction(credentials));
    
  2. Modifying the Saga:

    Update your Saga to handle the login request and trigger appropriate actions for success and failure:

    import { call, put, takeLatest } from 'redux-saga/effects';
    import * as API from './api'; // Assuming your API functions are imported from 'api.js'
    import { setItem } from './localStorage'; // Example function for localStorage interaction
    import { ACTION_TYPES } from './actionTypes';
    
    function* loginSaga(action) {
      try {
        const state = yield select(); // Access Redux state if needed
        const token = state.authReducer.token; // Example: Access token from Redux state
        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); // Example: Save token to localStorage
        // Optional: Return success data or trigger callback
      } catch (error) {
        yield put({ type: ACTION_TYPES.LOGIN_FAILURE, error });
        // Optional: Trigger error callback or handle error
      }
    }
    
    export function* loginMonitor() {
      yield takeLatest(ACTION_TYPES.LOGIN_REQUEST, loginSaga);
    }
    
  3. Handling Success and Error in Components:

    In your React component, you can connect Redux state and manage UI updates based on the success or failure actions dispatched by the Saga.

    import React from 'react';
    import { connect } from 'react-redux';
    import { loginAction } from './actions'; // Example action creator
    
    class LoginForm extends React.Component {
      handleLogin = () => {
        const { credentials, login } = this.props;
        login(credentials);
      };
    
      render() {
        return (
          <div>
            {/* Your login form UI */}
            <button onClick={this.handleLogin}>Login</button>
          </div>
        );
      }
    }
    
    const mapStateToProps = (state) => ({
      credentials: state.auth.credentials, // Example: Map credentials from Redux state
    });
    
    const mapDispatchToProps = (dispatch) => ({
      login: (credentials) => dispatch(loginAction(credentials)),
    });
    
    export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
    

Explanation:

  • Saga Logic:

    • loginSaga: Handles the asynchronous login process using call effect to call the API function (API.login).
    • On successful login (LOGIN_SUCCESS), it dispatches an action with the fetched data (payload).
    • On login failure (LOGIN_FAILURE), it dispatches an action with the error.
  • Component Interaction:

    • The component (LoginForm) dispatches the loginAction on a user action (e.g., button click).
    • It's connected to Redux using connect, mapping necessary state (like credentials) and dispatching actions (loginAction).

Handling Callbacks:

In Redux Saga, you typically handle success and error scenarios by dispatching appropriate actions (LOGIN_SUCCESS, LOGIN_FAILURE) from the Saga itself. Any further UI updates or logic based on these actions can be managed within your Redux-connected components.

If you need to perform additional actions or callbacks based on the outcome of the Saga operation (e.g., redirecting to another page or showing alerts), you can:

  • Component Level: Listen to changes in Redux state (LOGIN_SUCCESS, LOGIN_FAILURE) and trigger local component actions accordingly.
  • Saga Level: Directly trigger side-effects (like navigation or alerts) using libraries like react-router for navigation or showing alerts directly within Sagas (though this is less common for UI interactions).

By following this approach, you maintain the separation of concerns between business logic (handled in Sagas) and UI interactions (managed in components), ensuring a clear and manageable code structure in your React Native application.