Registration - Part 2


In this video we are continuing on with our User Registration journey. By now a user can visit the /register route, and they can see the form. But when they fill in the form, and click submit, not very much actually happens.

Before we fix that, let's add a link to the NavBar component so that we don't have to keep manually typing this in every time we want to try registration.

There's not a lot too this. We've already added in the Auth Aware NavBar way back in Video 11. At this stage, all we need to do is add an extra link to the "not logged in" part of the conditional:

// /src/components/NavBar.js

import React from 'react';
import {Link} from 'react-router';
import {Navbar, NavbarBrand, Nav, NavItem, NavLink} from 'reactstrap';
import '../styles/navbar.scss';

let NavBar = (props) => {

  const loginOrProfile = (auth) => {

    return auth.isAuthenticated ?
      <Nav className="float-xs-right" navbar>
        <NavItem className="navbar-text">
          <Link to="/profile">Welcome back {auth.username}</Link>
        </NavItem>
        <NavItem>
          <NavLink tag={Link} to="/logout">Logout</NavLink>
        </NavItem>
      </Nav>

      :

      <Nav className="float-xs-right" navbar>
        <NavItem>
          <NavLink tag={Link} to="/register">Register</NavLink>
        </NavItem>
        <NavItem>
          <NavLink tag={Link} to="/login">Log in</NavLink>
        </NavItem>
      </Nav>;
  };

  return (
    <div>
      <Navbar color="inverse" dark full>
        <NavbarBrand href="/">Our Cool App</NavbarBrand>
        {loginOrProfile(props.auth)}
      </Navbar>
    </div>
  );
};

NavBar.propTypes = {
  auth: React.PropTypes.object.isRequired
};

export default NavBar;

And that's cool - good enough to satisfy our requirements for a link to the Registration page in our NavBar. And only visible if not logged in.

However, now let's get onto the more interesting part - handling a registration form submission.

Handling Registration Form Submissions

Much like in the previous video, there is nothing particularly new here. We already know the process. Let's work through it.

We start off by visiting the /register path in our browser, filling in the form fields, and submitting the form.

We have told our form to call a specific method - handleRegistration - whenever this form is submitted. In doing so, we also know by now that we will receive an object containing some form data. In our case, we have simply called this argument formData:

// /src/containers/RegistrationPage.js

class RegistrationPage extends React.Component {

  handleRegistration(formData) {
  }

As part of the previous video, we have also defined some constants for the various actions that will be dispatched during this process.

We can therefore now start by calling dispatch and passing in the form data as our payload:

import * as types from '../constants/actionTypes';

// * snip *

  handleRegistration({username, email, newPassword, newPasswordRepeated}) {
    this.props.dispatch({
      type: types.REGISTER__REQUESTED,
      payload: {
        username,
        email,
        newPassword,
        newPasswordRepeated
      }
    });
  }

Ok, this largely looks like normal, with one exception.

Previously we had:

handleRegistration(formData) {

and now we have:

handleRegistration({username, email, newPassword, newPasswordRepeated}) {

One really nice feature of ES6 - as we have seen quite a lot in the course - is destructuring assignment.

One super nice way to use destructuring is in method / function signatures.

Taking the first example, we may end up with code like:

import * as types from '../constants/actionTypes';

// * snip *

  handleRegistration(formData) {

    const {username, email, newPassword, newPasswordRepeated} = formData;

    this.props.dispatch({
      type: types.REGISTER__REQUESTED,
      payload: {
        username,
        email,
        newPassword,
        newPasswordRepeated
      }
    });
  }

Here we extract the four variables out of the formData, and then go on to use them as normal in our payload.

However, we can skip this step entirely, and destructure them from the passed in formData object directly in the signature:

handleRegistration({username, email, newPassword, newPasswordRepeated}) {

Same thing.

Ok, there is one major downside to this. If you don't understand what's happening, this is confusing as heck. This becomes a training issue.

Thankfully, you do understand it. If you have colleagues / team members who potentially don't, please feel free to send them here - or any other resource on the subject.

Saga: JavaScript For The Over 50s

I better explain the title - I know my humour is lost on many. Saga - here in the UK - is a very prominent insurance company for people aged 50 and over. I've been smashed with their advertising since I was a young lad, and I can't help always think of them when using Redux Saga.

Anyway, now that we are dispatching an action, we need some interested party to act upon it.

We could do so by adding in a new Reducer. However, first we need to go and talk to our API - you know, for the whole Registration thing?

If we're doing things like API calls, interacting with the file system, or anything else that might go wrong, then first we need to manage that. And in our case we are using Redux Saga for this. Hopefully at this stage, that's not news to you :)

Ok, so we need a new Saga.

We will start by adding in the basic "shell" which we have used in pretty much every saga so far.

// /src/sagas/register.saga.js

import * as api from '../connectivity/api.register';
import {call, put} from 'redux-saga/effects';
import {takeLatest} from 'redux-saga';
import * as types from '../constants/actionTypes';

export const REQUESTS = {
  REGISTER__DOREGISTRATION__SAGA: 'profile.doRegistration.saga',
};

export function *doRegistration(action) {

  try {

    yield put({
      type: types.REQUEST__STARTED,
      payload: {
        requestFrom: REQUESTS.REGISTER__DOREGISTRATION__SAGA
      }
    });

  } catch (e) {

    yield put({
      type: types.REQUEST__FAILED,
      payload: {
        message: e.message,
        statusCode: e.statusCode
      }
    });

  } finally {

    yield put({
      type: types.REQUEST__FINISHED,
      payload: {
        requestFrom: REQUESTS.REGISTER__DOREGISTRATION__SAGA
      }
    });

  }

}

export function *watchRequestRegistration() {
  yield* takeLatest(types.REGISTER__REQUESTED, doRegistration);
}

And of course, because we have added a new watch function, we need to add this to our rootSaga:

// /src/sagas/index.js

import {fork} from 'redux-saga/effects';
import * as authSaga from './auth.saga';
import * as profileSaga from './profile.saga';
import * as registerSaga from './register.saga';

export default function *rootSaga() {
  yield [
    fork(authSaga.watchLogin),
    fork(authSaga.watchLoginSucceeded),
    fork(authSaga.watchLoginFailed),

    fork(authSaga.watchLogoutRequested),

    fork(profileSaga.watchChangePassword),
    fork(profileSaga.watchChangePasswordSucceeded),
    fork(profileSaga.watchChangePasswordFailed),
    fork(profileSaga.watchRequestProfile),

    fork(registerSaga.watchRequestRegistration),
  ];
}

Now we can get on with implementing the specifics of this particular function.

The Real Registration

We've got all our pieces in place, and now we need to talk to our API.

We will do this - as we have seen many times before - by using a call. This call will call a function which POST's our form data off to our API. Let's implement this:

// /src/connectivity/api.register.js

import asyncFetch from './async-fetch';
import {getBaseRequestConfig} from './baseRequestConfig';

export async function register(username, email, newPassword, newPasswordRepeated) {

  const baseRequestConfig = getBaseRequestConfig();

  const requestConfig = Object.assign({}, baseRequestConfig, {
    method: 'POST',
    body: JSON.stringify({
      "username": username,
      "email": email,
      "plainPassword": {
        "first": newPassword,
        "second": newPasswordRepeated
      }
    })
  });

  /* global API_BASE_URL */
  const url = API_BASE_URL + '/register';

  const response = await asyncFetch(url, requestConfig);

  return await response.json();
}

Again, hopefully at this stage this is not new to you. If it is, please watch earlier videos in this series where these functions are explained in much more detail.

The main point here is that we accept four arguments:

username, email, newPassword, newPasswordRepeated

We can then do whatever we like with these arguments. As mentioned in the previous video, our API expects the registration data to arrive in a very specific shape:

# Symfony 3 API codebase

# /src/AppBundle/Features/register.feature

  Scenario: Can register with valid data
    When I send a "POST" request to "/register" with body:
      """
      {
        "email": "gary@test.co.uk",
        "username": "garold",
        "plainPassword": {
          "first": "gaz123",
          "second": "gaz123"
        }
      }
      """
    Then the response code should be 201
     And the response should contain "The user has been created successfully"
    When I am successfully logged in with username: "garold", and password: "gaz123"
     And I send a "GET" request to "/profile/2"
     And the response should contain json:
      """
      {
        "id": "2",
        "username": "garold",
        "email": "gary@test.co.uk"
      }
      """

We're only customising to this shape at the very last moment.

Anyway, with this function in place we can now make a call to register from our Saga.

There is one other interesting point here. We know that if we get back a successful registration response, it will have two fields - msg, and token.

We can now update our saga function accordingly:

// src/sagas/register.saga.js#L13

import * as api from '../connectivity/api.register';
import {call, put} from 'redux-saga/effects';
import {takeLatest} from 'redux-saga';
import * as types from '../constants/actionTypes';

export const REQUESTS = {
  REGISTER__DOREGISTRATION__SAGA: 'profile.doRegistration.saga',
};

export function *doRegistration(action) {

  try {

    yield put({
      type: types.REQUEST__STARTED,
      payload: {
        requestFrom: REQUESTS.REGISTER__DOREGISTRATION__SAGA
      }
    });

    const {username, email, newPassword, newPasswordRepeated} = action.payload;

    const {msg, token} = yield call(api.register, username, email, newPassword, newPasswordRepeated);

  } catch (e) {

  // * snip *

We saw earlier how handleRegistration will dispatch an action containing the payload of our username, email, newPassword, and newPasswordRepeated. Once more, we use ES6 destructuring to pull these out of the action.payload object.

Then, we can pass these into the call to api.register - the function we just created.

We also know that if this call goes to plan, we should expect an object to be returned with both the msg and token keys. So, we can again destructure these from the outcome of the yield call. Fairly awesome, and very succinct.

At this point we should be able to register.

However, in doing so we now have two final problems. Firstly, we don't see any indication of a success, should the registration be successful.

It would be very nice if we were redirected to some other page - perhaps to the /profile page.

Secondly, we also don't see any indication of errors should the registration fail.

We will wrap up by fixing both of these problems in the next, and last video.

Code For This Course

Get the code for this course.

Episodes