Login - Part 1


In this video we are going to start building up our front-end Login functionality. It is important to point out that to follow along, you must have some form of working API.

Now, the good news is that if you do not have a working API, then you can use the same one that is used throughout this series. It's made in Symfony 3, using FOSUserBundle, FOSRESTBundle, and LexikJWTAuthenticationBundle. Oh boy, that's a lot of bundles.

If you are the kind of maverik who sees code like Neo sees the Matrix, then you can grab the API code right here. If you aren't yet sure what any of this means, I'd advise you to first follow along with the course that walks through this code, where you will learn how the back end API is put together.

Once you have the API online, in our case, we are going to run the Behat tests to ensure that the Login functionality is working as expected:

Feature: Handle user login via the RESTful API

  In order to allow secure access to the system
  As a client software developer
  I need to be able to let users log in and out

  Background:
    Given there are Users with the following details:
      | id | username | email          | password |
      | 1  | peter    | peter@test.com | testpass |
      | 2  | john     | john@test.org  | johnpass |
      | 3  | tim      | tim@blah.net   | timpass  |
     And I set header "Content-Type" with value "application/json"

  Scenario: User can Login with good credentials (username)
    When I send a "POST" request to "/login" with body:
      """
      {
        "username": "peter",
        "password": "testpass"
      }
      """
    Then the response code should be 200
     And the response should contain "token"

This is the Behat feature spec for our Login flow. If the test passes, it proves that we can login to our API by sending in JSON in the format:

{
"username": "peter",
"password": "testpass"
}

And we should expect a response from our API with a 200 status code, and a body containing a token. This token will be a JSON Web Token, or JWT (pronounced jot). We will need to use this token for any subsequent requests to our API, for example when fetching our user profile information.

Planning Our Login Form Component

Disregarding the fact that our form is about 'Login', we essentially need to create a component that is a form.

Our form component will contain all the fields - the username, and password input fields - and also the submit button. Where things become a little more confusing is that this component won't actually know what happens when the form is submitted.

Instead, it relies on another component to pass in the function to call when the form is submitted. This function to call on form submit is passed in as a prop.

The login form isn't the best example of why this is useful. Instead, maybe think about your user profile.

In the profile we may have your age, email, and bio fields.

This form is going to be the same whether you want to add your profile data for the first time, or edit your existing profile data.

However, what happens when you submit this form will differ, depending on whether this is the first time you are submitting, or if this is an update. In the case of a new profile, we may want to POST the data. In the case of an update, a PUT, or PATCH would be a better choice.

Therefore, we could re-use the same form, but have two different 'wrappers'. I don't think this is set in stone, but it is very common to see these 'wrapper' components referred to as 'Containers'.

In our profile example, we may have a AddProfileContainer, and an EditProfileContainer. Both would look extremely similar, importing the Login Form component, and defining an onSbumit function. Where they would differ is in the implementation of this onSubmit function - the AddProfileContainer calling a POST, whereas the EditProfileContainer may call a PUT or PATCH.

Now, that's not strictly true anyway, as they wouldn't directly call the API - instead, they would delegate to a saga, but more on that shortly.

Anyway, after all this we can deduce that our LoginForm component is going to need some sort of LoginFormContainer to contain the function to call on submit, and so on. Instead of calling this a LoginFormContainer, it makes more sense to simply call this the LoginPage, but it will live inside the src/containers directory.

Updating The State With Form Data

It's hard to get through a web project without having at least one form in your project. Most projects have several, and many projects are entirely about forms.

It seems odd, therefore, that working with forms in React can be quite painful.

That I am aware of, there are two approaches to working with forms.

You can go with simple forms, which have no logic to keeping the state updated as the user types / changes inputs. These forms only care about updating the state whenever the user finally submits the form. Again, these are simple, but certain features such as validation as you type are not easy to implement.

On the opposite side to this are more complicated forms, whereby each field updates the state as you type / change inputs. This is great for e.g. the on-demand validation, but adds complexity in terms of keeping the state in sync with the form field values at all times.

Redux Form aims to solve this problem, and largely does a very good job of it in the process. The only downside? It can be quite complicated to understand just what is happening under the surface. One part in particular - the passing of the submit function - is particularly confusing, in my opinion.

Ultimately it is a trade off - and for me, Redux Form wins out.

Implementing Our Login Form Component

Knowing the above, let's now look at the code from the LoginForm component.

// src/components/LoginForm.js

import React from 'react';
import {Field, reduxForm} from 'redux-form';

const LoginForm = () => {

  return (
    <form>

      <Field component="input"
             name="username"
             id="username"
             type="text"
             placeholder="Username or email address"
             required="required"
      />

      <Field component="input"
             name="password"
             id="password"
             type="password"
             placeholder="Password"
             required="required"
      />

      <button type="submit">
        Login
      </button>
    </form>
  );

};

export default reduxForm({
  form: 'login'
})(LoginForm);

Whenever we work with React, we always start by importing react.

Next, we are going to import the Field component from redux-form. This is how we connect the user's input to our Redux store. You can dive further in to the Field component in the Redux Form docs. The important parts for us are the props of component, and name.

In our case we are using the simple string value of input for our component, which renders a text input. Other choices here are select and textarea. You can, of course, get much fancier with your form fields, but we need not worry about that at this stage.

Our fields must be named, and the names themselves are self explanitory.

All other information is optional, and is simply passed as props to the element generated by the component prop - the input element, in our case.

The submit button is standard HTML. Nothing fancy here.

To hook this form up to Redux Form, we must wrap the LoginForm component inside the reduxForm higher order component. In other words, the reduxForm function is going to be called with our form is its parameter, and an object telling the function what this form is called - login in this case. This name has to be unique throughout our project.

You can pass other information into this object, such as validation rules, initial field values, or even define the function to call when this form is submitted. But we aren't doing any of that at this stage.

If you were to use this form now, it wouldn't actually submit as expected. We have yet to set up the onSubmit function.

What you would notice though is that for every interaction you have with the form, Redux Form is going to dispatch an action describing the change you just made. This can be as simple as putting your cursor into a field and clicking - giving focus to the field - or more complex, in so much as passing in the currently field value to be set as the new state for that field. This will unfortunately fill up your console log very quickly with redux-logger messages.

However, there is another issue - you may notice that you can type, but the characters you type never show in the field itself. This is because we haven't hooked up the Redux Form reducer. Let's fix that:

// /src/reducers/index.js

import { combineReducers } from 'redux';
import {routerReducer} from 'react-router-redux';
import {reducer as formReducer} from 'redux-form';

const rootReducer = combineReducers({
  form: formReducer,
  routing: routerReducer
});

export default rootReducer;

Simple enough - import the formReducer, and include it in the object consumed by combineReducers.

Now our form fields should work as expected. Only, when the form is submitted, it is still not working. Let's fix that also.

// /src/containers/LoginPage.js

import React, {Component} from 'react';
import LoginForm from '../components/LoginForm';

class LoginPage extends Component {

  doLogin(formData) {
    console.log('form data was received', formData);
  }

  render() {
    return (
      <LoginForm
        onSubmit={this.doLogin.bind(this)}
      />
    );
  }

}

export default LoginPage;

Here, we have started by import'ing React.

Then, we import the LoginForm. Note that we are in the /src/containers directory, so need to go up a directory, and into the components directory to get the LoginForm.

Rather than making this a stateless function, instead our LoginPage is a class which extends React.Component.

Our render method outputs the form, but critically, it passes in the onSubmit prop, which references the doLogin function inside this class.

We must also update the LoginForm component to accept this onSubmit prop, and then use it when the form is submitted:

// /src/components/LoginForm.js

import React from 'react';
import {Field, reduxForm} from 'redux-form';

const LoginForm = (props) => {

  return (
    <form onSubmit={props.handleSubmit}>

      <Field component="input"
             name="username"
             id="username"
             type="text"
             placeholder="Username or email address"
             required="required"
      />

      <Field component="input"
             name="password"
             id="password"
             type="password"
             placeholder="Password"
             required="required"
      />

      <button type="submit">
        Login
      </button>
    </form>
  );

};

LoginForm.propTypes = {
  onSubmit: React.PropTypes.func.isRequired
};

export default reduxForm({
  form: 'login'
})(LoginForm);

Note the additions here.

Firstly we now pass in props to our LoginForm function.

Next, we define an onSubmit event listener, and use the value of props.handleSubmit. This is confusing, as didn't we pass in a prop called onSubmit?

Why, yes we did. More on this in a second.

For now, let's skip that and cover that because we have defined a prop called onSubmit, and passed it in, we should update the LoginForm.propTypes to serve as extremely useful documentation to ourselves, and any other developers who use this component as to what props are expected, and how they should behave. In this case, our onSubmit prop is expected to be a func (function), and is required.

Ok, but coming back to this onSubmit / handleSubmit thing. I have to say, I found this entirely confusing. I have scoured the documentation, GitHub tickets, and several pages of Google for various searches and I haven't found documentation that explained this succinctly.

As best I understand it, the props.handleSubmit function is defined by Redux Form.

Redux Form's props.handleSubmit function will expect us to pass in our own function as a prop called props.onSubmit.

By following this convention, we don't need to explicitly call props.handleSubmit(props.onSubmit), which instead happens implicitly.

I'm still not sure I understand this fully, but that's how the code reads to me. Feel free to correct me if I have this wrong.

Anyway, this works, and if you submit the form now, you should see the console log statement showing whatever form data you just submit.

This is cool as this now enables us to proceed further, using this form data to send to our Redux Saga which will handle the process of login for us. This is what we will move on to in the very next video.

Code For This Course

Get the code for this course.

Episodes