« Previous Tutorial Next Tutorial »

In our last tutorial, we started the process of wiring our log in form to our API. We had to stop with a blank file and nothing working, which is super unsatisfying! We’re going to fix that today, and you’re going to see the login work … albeit only in your browser’s dev tools. Baby steps, folks.

Let’s jump right in. Your server should still be running from last time, but if it’s not, fire it up in development mode (I think at this point I can stop telling you how to do that, right?). Then switch over to Sublime Text and open /src/components/account/LoginPageContainer.jsx. This file should be blank. If for some reason it’s not, it means you either decided to forge ahead, or your computer is haunted by ghosts … possibly from the future. Consider calling an exorcist before continuing.

Anyway, here are the first eight lines you need to add. They’re all imports:

import React from 'react';
import 'whatwg-fetch';

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { incrementProgress, decrementProgress } from '../../actions/progress';

import LoginPage from './LoginPage';

We grab two modules, then some functionality, and then our LoginPage component. Nothing here should be too confusing, but just a reminder that imports done with curly braces mean you’re pulling individual variables or methods from a file that contains more than one, and that each of them are then accessible within your code by those names (for example, incrementProgress).

The next thing we need to build is our component. We’re going to use a class here, and not just a pure function, because we need to bind a few helper methods. Here’s the skeleton:

export class LoginPageContainer extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
        <LoginPage loginFunction={this.attemptLogIn} />
      </div>
    );
  }
}

Don’t worry about the fact that this.attemptLogin doesn’t exist yet. We’ll fix that in a second. Below that we’re going to need to connect our component to Redux so we can use those progress actions (not to mention other actions we’re going to write later). So, below the component, add the following:

function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    incrementProgressAction: incrementProgress,
    decrementProgressAction: decrementProgress,
  }, dispatch);
}

export default connect(null, mapDispatchToProps)(LoginPageContainer);

All right, let’s add that attemptLogin method. Below the constructor block in our component class, but above the render block, add the following:

  async attemptLogIn(userData) {
    const { decrementProgressAction, incrementProgressAction } = this.props;

    // turn on spinner
    incrementProgressAction();

    // contact login API
    const loginResponse = await fetch(
      // where to contact
      '/api/authentication/login',
      // what to send
      {
        method: 'POST',
        body: JSON.stringify(userData),
        headers: {
          'Content-Type': 'application/json',
        },
        credentials: 'same-origin',
      },
    );

    console.log(loginResponse);

    // turn off spinner
    decrementProgressAction();
  }

As you can see, we have another async function here, which allows us to use await to make sure we have responses from the server before proceeding. I love asyc/await so much! You’ll also notice that we turn on and off our spinner here (speaking of which, we’re going to need to turn that into an actual spinner pretty soon!). Finally, you may notice that this method takes a userData parameter. We’ll be providing that in LoginPage.jsx

One last thing to do here. We need to bind this method so it can use “this” correctly. We do that in the constructor block by adding a line after line 12 for padding, and then adding the following two lines:

    // bound functions
    this.attemptLogIn = this.attemptLogIn.bind(this);

We should be good here. Looking for a big block of coding showing what the completed file should look like? No problem!

import React from 'react';
import 'whatwg-fetch';

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { incrementProgress, decrementProgress } from '../../actions/progress';

import LoginPage from './LoginPage';

export class LoginPageContainer extends React.Component {
  constructor(props) {
    super(props);

    // bound functions
    this.attemptLogIn = this.attemptLogIn.bind(this);
  }

  async attemptLogIn(userData) {
    const { decrementProgressAction, incrementProgressAction } = this.props;

    // turn on spinner
    incrementProgressAction();

    // contact login API
    const loginResponse = await fetch(
      // where to contact
      '/api/authentication/login',
      // what to send
      {
        method: 'POST',
        body: JSON.stringify(userData),
        headers: {
          'Content-Type': 'application/json',
        },
        credentials: 'same-origin',
      },
    );

    console.log(loginResponse);

    // turn off spinner
    decrementProgressAction();
  }

  render() {
    return (
      <div>
        <LoginPage loginFunction={this.attemptLogIn} />
      </div>
    );
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    incrementProgressAction: incrementProgress,
    decrementProgressAction: decrementProgress,
  }, dispatch);
}
export default connect(null, mapDispatchToProps)(LoginPageContainer);

All right, save the file and then open up /src/components/account/LoginPage.jsx … there’s a bunch to do here as well. Remember how ESLint was complaining that we didn’t need to use a class, here? Well, now we do, because we’re going to be manipulating component state to create “controlled” forms. This means React handles their values, even when a user is manipulating them. Every change is stored in component state, and the forms always render what’s in that state, meaning if you change it programmatically, they’ll display the new string automatically.

In your constructor block, under the super line, add the following:

    // component state
    this.state = {
      email: '',
      password: '',
    };

This sets our component state, which we talked about before in Tutorial 35. Now we need some methods to manipulate things. Below your constructor block, but above your render block, add the following lines of code:

  // update state as email value changes
  handleEmailChange(e) {
    this.setState({ email: e.target.value });
  }

  // update state as password value changes
  handlePasswordChange(e) {
    this.setState({ password: e.target.value });
  }

  compileFormData() {
    const { loginFunction } = this.props;
    const formData = this.state;
    loginFunction(formData);
  }

As you can see, we’ve got two methods which will update component state with the values of the form fields as they change, and a method for snagging the form data from the state (which we’ll call when they click the submit button). This’ll send that data back to our container, which will then pass it on to the API.

We need to bind these functions, so back up in our constructor block, below the super line and above the state declaration, add this code:

    // bound functions
    this.compileFormData = this.compileFormData.bind(this);
    this.handleEmailChange = this.handleEmailChange.bind(this);
    this.handlePasswordChange = this.handlePasswordChange.bind(this);

Last thing we need to do is update our form fields and submit button in our render block. Here’s the change for the email field:

              <Input
                type="email"
                name="email"
                id="userEmail"
                placeholder="noreply@musiclist.com"
                value={this.state.email}
                onChange={this.handleEmailChange}
              />

Here’s the change for the password field:

              <Input
                type="password"
                name="password"
                id="userPassword"
                placeholder="password"
                value={this.state.password}
                onChange={this.handlePasswordChange}
              />

Aaaaand here’s the button:

<Button onClick={this.compileFormData}>Log In</Button>

Here’s what your whole file should look like:

import React from 'react';
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';

export default class ProfilePage extends React.Component {
  constructor(props) {
    super(props);

    // bound functions
    this.compileFormData = this.compileFormData.bind(this);
    this.handleEmailChange = this.handleEmailChange.bind(this);
    this.handlePasswordChange = this.handlePasswordChange.bind(this);

    // component state
    this.state = {
      email: '',
      password: '',
    };
  }

  // update state as email value changes
  handleEmailChange(e) {
    this.setState({ email: e.target.value });
  }

  // update state as password value changes
  handlePasswordChange(e) {
    this.setState({ password: e.target.value });
  }

  compileFormData() {
    const { loginFunction } = this.props;
    const formData = this.state;
    loginFunction(formData);
  }

  render() {
    return (
      <div className="row justify-content-center">
        <div className="col-10 col-sm-7 col-md-5 col-lg-4">

          <Form>
            <FormGroup>
              <Label for="userEmail">Email</Label>
              <Input
                type="email"
                name="email"
                id="userEmail"
                placeholder=noreply@musiclist.com
                value={this.state.email}
                onChange={this.handleEmailChange}
              />
            </FormGroup>
            <FormGroup>
              <Label for="userPassword">Password</Label>
              <Input
                type="password"
                name="password"
                id="userPassword"
                placeholder="password"
                value={this.state.password}
                onChange={this.handlePasswordChange}
              />
            </FormGroup>
            <Button onClick={this.compileFormData}>Log In</Button>
          </Form>
        </div>
      </div>
    );
  }
}

Save this file, and open /src/components/Template.jsx. We need to change line 6 so that instead of importing LoginPage.jsx, it imports LoginPageContainer.jsx, like this:

import LoginPage from './account/LoginPageContainer';

Save that file, and we should be good to run a test! Make sure your dev server is running, and hop over to a browser and bring up your login page (I’d give it a hard refresh to be sure all of these changes have taken). Make sure to open your JavaScript console so you can see what’s going on.

For starters, let’s test a bad login. I used blah@blah.com with a password of 12345. Click the submit button and in between the actions for incrementing and decrementing the spinner count, you’ll get a nice 400 error in your console. This is expected and what we want. Now try using your actual login information. Assuming you’ve typed your email and password correctly, you should see a 200 response. Congrats, you’ve authenticated your user via the API!

That’s it for this one. In the next tutorial, we’ll make our application respond to correct (and not-so-correct) login attempts.

As always, it’s a good idea to commit your latest changes to your git repo. If you need a refresher on how to do that, check out Tutorial 8 and Tutorial 9.

See you next time!

« Previous Tutorial Next Tutorial »