« Previous Tutorial Next Tutorial »

Now that we have this lovely registration page, wouldn’t it be nice if it actually did something when you filled in the values and click the button? Seems like that would be useful, right?

We’re going to need a bunch of functionality to make this battlestation fully operational, so to speak, so let’s get started. In this tutorial we’re going to connect to the API, register the user, and log them in. In the next tutorial, we’ll cover building out a system that efficiently sends them off to a “thanks for registering!” page, so they’re not still staring at the registration form after successfully submitting it.

Let’s start by heading over to Sublime and opening /routes/api/authentication.js. Remember, we already built our API endpoint back in tutorial 37. We’re going to need to modify it a bit though. First off, remove the user argument from the callback function in line 51, like this:

  User.register(newUser, req.body.password, (err) => {

because we’re not going to use it anymore. Then replace lines 56 and 57:

    // Otherwise, for now, send back a JSON object with the new user's info
    return res.send(JSON.stringify(user));

With the following code:

    // Otherwise log them in
    return passport.authenticate('local')(req, res, () => {
      // If logged in, we should have user info to send back
      if (req.user) {
        return res.send(JSON.stringify(req.user));
      }
      // Otherwise return an error
      return res.send(JSON.stringify({ error: 'There was an error logging in' }));
    });

So first we add the user to the database, then we use the same credentials we used to add them, to log them in. Seamless! Of course, you might want to add email verification or other steps, but since this is a simple app and we’re not that worried about fake logins, we’re just going to go ahead and log them in immediately.

Save that file, and move on to /src/components/actions/authentication.js. We’re going to create two new action creator functions up at the top, and then a function to register the user with the API down at the bottom. Below line 9, add the following two lines:

export const registrationFailure = () => ({ type: 'AUTHENTICATION_REGISTRATION_FAILURE' });
export const registrationSuccess = () => ({ type: 'AUTHENTICATION_REGISTRATION_SUCCESS' });

Then head all the way to the bottom of the file (it should end on line 120). Our registration function is very similar to logUserIn, so I think you’ll be able to handle all of the code at once. Here it is:

// Register a User
export function registerUser(userData) {
  return async (dispatch) => {
    // turn on spinner
    dispatch(incrementProgress());

    // contact the API
    await fetch(
      // where to contact
      '/api/authentication/register',
      // what to send
      {
        method: 'POST',
        body: JSON.stringify(userData),
        headers: {
          'Content-Type': 'application/json',
        },
        credentials: 'same-origin',
      },
    )
    .then((response) => {
      if (response.status === 200) {
        return response.json();
      }
      return null;
    })
    .then(async (json) => {
      if (json) {
        await dispatch(loginSuccess(json));
        await dispatch(registrationSuccess());
      } else {
        dispatch(registrationFailure(new Error('Registration Failed')));
      }
    })
    .catch((error) => {
      dispatch(registrationFailure(new Error(error)));
    });

    // turn off spinner
    return dispatch(decrementProgress());
  };
}

The key thing to note here is that we’ve added another async / await setup once our API returns our user JSON after successfully registering and logging them in. That way, we ensure that the loginSuccess action creator runs first (which puts the user data in our state) and then the registrationSuccess action creator runs … which doesn’t do anything yet because we haven’t modified our reducer to listen for it. Generally, action creators are very fast, and these will almost always operate sequentially, but they are technically asynchronous functions, so better safe than sorry.

Save this file and head for /src/reducers/authentication.js. It’s time to do that listening we were just talking about. First off, below line 6, we need a new addition to our authentication object:

  registrationSucceeded: false,

We’re going to use this in the next tutorial to check and determine when to forward to our registration success page. It’s only ever going to be “true” for a very short period of time, but I’ll explain next time why it is we’re doing this.

Moving on, we’re going to piggyback on an existing case for a registration failure. Remove the opening brace from line 35 like this:

case 'AUTHENTICATION_LOGOUT_FAILURE':

and then just below it, add the following:

    case 'AUTHENTICATION_REGISTRATION_FAILURE': {

Again, we haven’t quite gotten to error handling yet, although we’re going to hit it very soon, because we now have enough possible error points to warrant it.

Now we need an entirely new case block to handle a registration success. Below line 39, add this code:

    case 'AUTHENTICATION_REGISTRATION_SUCCESS': {
      const newState = Object.assign({}, state);
      newState.registrationSucceeded = true;
      return newState;
    }

This is why we wanted to make sure the login action runs first. That way state will be full of nice user info, rather than the empty values from initialState. Then we just change registrationSucceeded to true, and we’re good to go. Save this file and let’s head into the home stretch. Open up /src/components/account/RegistrationPageContainer.jsx. Under line 2, let’s import our registration action like this:

import { registerUser } from '../../actions/authentication';

Let’s also map our authentication state to props, so we can make it so that only logged-out users can view the registration page. Don’t worry, we’ll put up some messaging explaining what’s going on to those who’re logged in. At the bottom of the file, add a spacing line below line 28 and then, below that (so, on line 30), add this code:

const mapStateToProps = state => ({ authentication: state.authentication });

This is a condensed version of the mapStateToProps function we’ve used elsewhere, using ES6 implicit returns. It’s just saying “return the authentication part of the Store as props.” We need to pass this function to connect, so modify the final line of the file to look like this:

export default connect(mapStateToProps)(RegisterPageContainer);

Now let’s fix our register function on line 15. Delete the console.log on line 18 entirely, and uncomment line 17. That’s it! Hooray, foresight. Directly under line 20, our render() block, add the following:

    const { isLoggedIn } = this.props.authentication;

Then add a padding line and below that, add the following code:

    // User needs to be logged out to register
    if (isLoggedIn) {
      return (<p>Please log out before registering a new user</p>);
    }

    // Otherwise display the form

We’re all set, but we can simplify lines 30 to 34:

    return (
      <div>
        <RegisterPage registerFunction={this.registerFunction} />
      </div>
    );

a bit by doing them on one line. We don’t need those wrapping divs. So change those lines to this:

    return <RegisterPage registerFunction={this.registerFunction} />;

Nice and clean. You might be wondering why I don’t also do single-line, bracket-less if statements. The answer is because I don’t wanna! Unlike return, if statements don’t take a semi-colon at the end, which can lead to all sorts of issues if you go bracket-less and then add additional lines without paying attention. I prefer to use the brackets, and if the line is more than a few characters long (eg: if (user) { return true }) then I prefer to span it out over multiple lines.

Anway, we’re done. Did I mention that? Save the file. The entire thing should look like this:

import React from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { registerUser } from '../../actions/authentication';

import RegisterPage from './RegisterPage';

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

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

  registerFunction(userData) {
    const { dispatch } = this.props;
    dispatch(registerUser(userData));
  }

  render() {
    const { isLoggedIn } = this.props.authentication;

    // User needs to be logged out to register
    if (isLoggedIn) {
      return (<p>Please log out before registering a new user</p>);
    }

    // Otherwise display the form
    return <RegisterPage registerFunction={this.registerFunction} />;
}

const mapStateToProps = state => ({ authentication: state.authentication });

export default connect(mapStateToProps)(RegisterPageContainer);

Now we can head over to a browser. Let’s log in first and make sure we get our “yo, you gotta log out” message. So run a successful login, and then head for localhost:3000/account/register. Yep, there’s our message. Excellent. Now click “log out” in the header and witness React auto-change you over to the registration form. Sexy!

Fill in some details and register a user. If you have your console open, or your redux devtools, you’ll see your various actions firing. You’ll also note that the header correctly changes to the logged-in state. Congrats, you’ve connected your app to the registration API.

In the next tutorial, we’ll handle forwarding to the success page, setting the registrationSucceeded value back to false, and a little bit of cleanup. Until then!

« Previous Tutorial Next Tutorial »