Creating Saga

When a user enters their login credentials and hits the submit button, we are already dispatching the correct action. Now we are going to listen to that action using Saga. To do so, we create our first Saga inside app/Login/saga.js:

import request from 'utils/request';
import { notification } from 'antd';
import { call, put, takeLatest } from 'redux-saga/effects';
import { LOGIN_REQUEST } from './constants';
import { onLoginSuccess, onLoginFailure } from './actions';

export function* onLoginRequest(action) {
try {
const { success, user, message } = yield call(request, 'api/users/signin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(action),
});

if (!success) {
throw message;
}

yield put(onLoginSuccess(user));
} catch (err) {
notification.error({
message: 'Login Failure',
description: err.toString(),
});

yield put(onLoginFailure(err.toString()));
}
}

export default function* data() {
yield takeLatest(LOGIN_REQUEST, onLoginRequest);
}

Does the snippet look like quite a mouthful? Well, don't worry about it. It is just a matter of time until you get used to Saga functions. Note the following things:

  • Redux Saga provides some important factory functions (call, put, and takeLatest) to request, call, and observe actions. takeLatest is a helper factory function from Saga that triggers a new generator data function.
  • If you remember the Login container, we dispatched the onLoginRequest action creator when the user submits the form:
export const mapDispatchToProps = dispatch => ({
onSubmit: e => dispatch(onLoginRequest(e.toJS())),
});
  • The onLoginRequest function dispatches a LOGIN_REQUEST action. There is nothing new here:
export const onLoginRequest = user => ({ type: LOGIN_REQUEST, user });
  • In the Saga snippet, we are observing the LOGIN_REQUEST action continuously. That is, whenever the user dispatches this action, the onLoginRequest generator function will be called. 
  • The onLoginRequest generator function inside the Saga file takes the action as an argument and makes the REST API request using a factory function call from the Saga. 
  • Note that we have also created a utility function called request, which will help us call the API. If you check the request function (in the following snippet), it takes a request URL and options as arguments. For example, when making a login request, the URL would be http://localhost:3000/api/users/signin and the options include POST as the HTTP method, headers, and body. The request function uses the fetch method to make an API call:
function parseJSON(response) {
if (response.status === 204 || response.status === 205) {
return null;
}
return response.json();
}

function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}

const error = new Error(response.statusText);
error.response = response;
throw error;
}

export default function request(url, options) {
return fetch(url, options)
.then(checkStatus)
.then(parseJSON);
}
  • It is essential that we wrap the HTTP request call inside a try and catch block to handle all sorts of exceptions that may occur during the fetch request. 
  • The request function makes the call and receives success, user, and message as the return values. We save them in separate variables and if success is true, we invoke the onLoginSuccess action creator. 
  • If the API throws an error, we catch the errors in the Saga and invoke the onLoginFailure action creator. It is worth noting that instead of invoking the asynchronous request directly, the call factory function returns only a plain object with instructions for the middleware to dispatch the action. redux-saga takes care of the invocation and returns the result to the generator. The put factory function works in a similar fashion. For example, if there is an error, the put method will give a plain object as instruction, that is, { type: LOGIN_FAILURE, message }, which tells the Saga middleware to dispatch the LOGIN_FAILURE action with a proper message. 
  • We have also utilized the notification component from antd to notify the users with proper alerts. 

Now, the last piece of the puzzle is to provide a Saga to the store. Remember, we have already connected the Redux middleware to the store. To inject Saga to the Login container, we are going to use a higher-order function. So, inside app/containers/Login/index.js, we will inject Saga:

const withSaga = injectSaga({ key: 'login', saga });

export default compose(
withSaga,
withConnect,
)(LoginPage);

injectSaga is provided in app/utils/injectSaga.js. The function takes the Saga key and file and injects it into the container component. If you want to know how this works, look at the code found in the file:

import React from 'react';
import PropTypes from 'prop-types';
import hoistNonReactStatics from 'hoist-non-react-statics';

import getInjectors from './sagaInjectors';

export default ({ key, saga, mode }) => WrappedComponent => {
class InjectSaga extends React.Component {
static WrappedComponent = WrappedComponent;

static contextTypes = {
store: PropTypes.object.isRequired,
};

static displayName = `withSaga(${WrappedComponent.displayName ||
WrappedComponent.name ||
'Component'})`;

componentWillMount() {
const { injectSaga } = this.injectors;

injectSaga(key, { saga, mode }, this.props);
}

componentWillUnmount() {
const { ejectSaga } = this.injectors;

ejectSaga(key);
}

injectors = getInjectors(this.context.store);

render() {
return <WrappedComponent {...this.props} />;
}
}

return hoistNonReactStatics(InjectSaga, WrappedComponent);
};

The injectSaga function takes key, saga, and mode as arguments, where key is the name of the saga key, saga is the path of the Saga file, and mode has a default value of RESTART_ON_REMOUNT

A mode argument can take three constants:

  • RESTART_ON_REMOUNTmode starts a Saga when a component is being mounted and cancels with task.cancel() when a component is un-mounted to improve performance. 
  • DAEMON: mode starts a Saga when a component is mounted and never cancels it or starts again.
  • ONCE_TILL_UNMOUNT: mode is similar to RESTART_ON_REMOUNT but it does not run the Saga again. 

We are now ready to test our login system. So, try to run your application. To do so, first make sure you have your MongoDB instance running. After that, from the root of the project in your console, run yarn start. Open the browser console (https://support.airtable.com/hc/en-us/articles/232313848-How-to-open-the-developer-console). Go to the Network tab and choose the XHR tab. With that open, enter the login credentials and hit Enter:

{
"email":"[email protected]",
"password":"123123"
}

If you did everything correctly, you should be able to see the correct request and response format, as shown in the following screenshot:

Figure 6.3: Request and response in the browser console
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset