Registration - Part 1


In this video we are going to get started implementing the Registration journey. In my experience. the process of Registration can feel a little overwhelming. It's one of the first parts that a user of our site will experience, and if it goes wrong it leaves a terrible first impression.

Thankfully though, by now we have a robust process to work through. We've also created most of the components we will need to make this work. Also, we have a well tested backend Symfony 3 API to talk too, so that's over half the battle already in our favour.

Taking a quick look at the Behat feature for Registration:

# 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 can see that we need to send our data in a specific 'shape'. This isn't a a 1-to-1 mapping with how our front end will work, but that's no problem. As we go through, we will cover how easy it is to adapt our form data to meet any back end / API call requirements.

Along this journey we have also created a function for processing errors returned from a failed Symfony form submission (which is how any of our API calls really work) and as such can easily display these on any form field in our application. Again, another massive headache that we've already addressed.

The last big hurdle is retrieving and saving the JSON Web Token (JWT). As part of our API registration response, we do return this token - again, saving ourselves a big headache. All we need to do is to ensure we save it locally - something we've already figured out how to do during the earlier Login videos.

I don't want you to think there is nothing left to do at this point. We do still have some outstanding tasks. But I do want to highlight that we have done a good chunk of the hard work already, and by following this same process of container > component > saga > reducer we have a reliable, reproducible step-by-step methodology to follow regardless of what function / feature we are implementing.

Registration Form Component

To begin with, we will create a new component for our Registration Form.

Here is the code:

// /src/components/RegistrationForm.js

import React from 'react';
import {Field, Fields, reduxForm} from 'redux-form';
import {Button} from 'reactstrap';
import FormField from './FormField';
import FormPasswordRepeatedFields from './FormPasswordRepeatedFields';

const RegistrationForm = (props) => {
  return (
    <form onSubmit={props.handleSubmit} className="form-registration-form">

      <Field component={FormField}
             name="username"
             type="text"
             label="Username"
             placeholder="Username"
             required="required"
             className="form-control"
      />

      <Field component={FormField}
             name="email"
             type="email"
             label="Email Address"
             placeholder="Email Address"
             required="required"
             className="form-control"
      />

      <Fields names={[ 'newPassword', 'newPasswordRepeated' ]} component={FormPasswordRepeatedFields}/>

      <Button type="submit"
              size="lg"
              block
              color="success"
      >
        {props.isSubmitting ?
          <span>
            <i className="fa fa-spin fa-spinner"/> Registering...
          </span>
          :
          <span>Register</span>
        }
      </Button>
    </form>
  );

};

RegistrationForm.propTypes = {
  isSubmitting: React.PropTypes.bool.isRequired,
  onSubmit: React.PropTypes.func.isRequired,
  handleSubmit: React.PropTypes.func.isRequired,
};

export default reduxForm({
  form: 'registration-form'
})(RegistrationForm);

There's lots going on in this component, but nothing we haven't seen before in this series.

As mentioned already, we are benefiting massively from re-use of existing components. For example, we are re-using the FormField component twice - once for capturing the username, and again for capturing the email. Because of the way we created this component, we can pass in a type of text or email and get the right HTML input setup, but all from a single component.

Likewise, we did quite a lot of work on making our form fields for capturing Password / Password Repeated already, so we should definitely re-use this. This is great as we already have all the error styling, labels, and any error messages properly sorted out. No extra work. Nice.

As we know by now, our component is largely concerned with presentation. It knows how the form should look, but relies on another component (a container component) to tell it how to behave. Let's create that container component now.

Registration Container Component

First, we have the typical 'shell' that we have been using throughout this course.

We have connected this container component to our application's state.

Personally, I don't use mapDispatchToProps, but you could do at this point. I find having dispatch available via this.props.dispatch to be sufficient for my needs. Feel free to read more about this and form your own opinions.

// /src/containers/RegistrationPage.js

import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import * as types from '../constants/actionTypes';

class RegistrationPage extends React.Component {

  render() {
    return ();
  }

}

RegistrationPage.propTypes = {
  dispatch: PropTypes.func.isRequired,
  pageState: PropTypes.object.isRequired,
};

function mapStateToProps(state) {
  return {
    pageState: state
  };
}

export default connect(mapStateToProps)(RegistrationPage);

At this point this container component isn't very interesting. Before we make it more interesting, let's ensure we can access this component when visiting the /register path inside our browser.

To make this work, we will also need to update our list of available routes.

// /src/routes.js

import RegistrationPage from './containers/RegistrationPage';

export default (
  <Route path="/" component={App}>
    <Route path="register" component={RegistrationPage}/>
  </Route>
);

Note that I have removed all the extra lines in this file for the sake of brevity.

This code says that when we hit the location of /register, we want to display the RegistrationPage container component. Because this Route is nested under another route (the route for the location of /) we will also inherit all the layout of App. This is why our RegistrationPage has the NavBar, and is inside a Bootstrap container layout.

Back inside our RegistrationPage container, let's now import the RegistrationForm, and hook it up:

// /src/containers/RegistrationPage.js

import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import RegistrationForm from '../components/RegistrationForm';
import * as types from '../constants/actionTypes';

class RegistrationPage extends React.Component {

  handleRegistration(formData) {
  }

  render() {
    return (
      <RegistrationForm
        onSubmit={this.handleRegistration.bind(this)}
        isSubmitting={this.props.pageState.request.isSubmitting}
      />
    );
  }

}

RegistrationPage.propTypes = {
  dispatch: PropTypes.func.isRequired,
  pageState: PropTypes.object.isRequired,
};

function mapStateToProps(state) {
  return {
    pageState: state
  };
}

export default connect(mapStateToProps)(RegistrationPage);

Again, nothing new here that we haven't covered previously throughout this course.

We import the RegistrationForm component.

We can then use this component inside our render method. This RegistrationForm expects two props.

We will need to pass in a function that will be called whenever the form is submitted. For now, we have defined this method, and ensured it is correctly bound to the RegistrationPage. We also know by now that when a form submission happens, this method will receive an object containing the submitted formData.

Secondly, we need a prop to tell the RegistrationForm if it is currently submitting. For the purposes of our registration form, we can go with the naive option. If you're unsure on this, please watch this video onwards.

New Actions

At this point we had better create ourselves some new action types. There's not a lot to this - we just need to define some new constants:

// /src/constants/actionTypes.js

export const REGISTER__REQUESTED = 'REGISTER__REQUESTED';
export const REGISTER__REQUEST__SUCCEEDED = 'REGISTER__REQUEST__SUCCEEDED';
export const REGISTER__REQUEST__FAILED = 'REGISTER__REQUEST__FAILED';

We will need this more as we go through the remainder of our Registration form implementation. Again, at this point this is hopefully not new to you.

Preventative Measures

At this point we should be able to visit the /register page in our browser and see the Registration Form display as expected.

If you fill in the form and click the submit button then not very much will happen. But hey, it looks like it might :)

More importantly though, at this point we should fix up a potential issue. As developers, we now need to stop our logged in users from being able to visit the /register route, and accidentally re-sign up. It sounds crazy, but I promise you, if you have enough users, and you can sign up when logged in, sooner or later one of these users will do it. Yep... some people just want to watch the world burn.

At this point we can cover something new: componentWillReceiveProps.

This is one of React's lifecycle methods.

In previous videos we have relied on componentDidMount to trigger dispatch'ing actions to load data required by our components. This worked well, but in this instance it would be nicer if we could check if the user is logged in every time the state in our application changes. This ensures we guard against any potential strangeness that the user may be up too.

By using componentWillReceiveProps, every single time our store is updated, the component will be aware of these changes, and can react accordingly. In this instance, we could check to see if the value of auth.isAuthenticated is true or false, and then if false, redirect the user back to the site root (/).

Implementing this conditional is simple enough:

// /src/containers/RegistrationPage.js

class RegistrationPage extends React.Component {

  componentWillReceiveProps(newProps) {
    if (newProps.pageState.auth.isAuthenticated) {
      this.props.router.replace('/');
    }
  }

However, note that we are using this.props.router ?

To be able to do this, we must wrap our component withRouter. Unfortunately, this does lead to harder to understand code :(

// /src/containers/RegistrationPage.js

import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {withRouter} from 'react-router';
import RegistrationForm from '../components/RegistrationForm';
import * as types from '../constants/actionTypes';

class RegistrationPage extends React.Component {

  componentWillReceiveProps(newProps) {
    if (newProps.pageState.auth.isAuthenticated) {
      this.props.router.replace('/');
    }
  }

  handleRegistration(formData) {
  }

  render() {
    return (
      <RegistrationForm
        onSubmit={this.handleRegistration.bind(this)}
        isSubmitting={this.props.pageState.request.isSubmitting}
      />
    );
  }

}

RegistrationPage.propTypes = {
  dispatch: PropTypes.func.isRequired,
  pageState: PropTypes.object.isRequired,
  router: PropTypes.object.isRequired
};

function mapStateToProps(state) {
  return {
    pageState: state
  };
}

export default connect(
  mapStateToProps
)(
  withRouter(RegistrationPage)
);

Note here the import statement, and the wrapping of withRouter inside the connect call.

Anyway, at this point we should have successfully stopped our users from being able to register when already logged in. What a result.

In the next video we will start to handle the registration process.

Code For This Course

Get the code for this course.

Episodes