« Previous Tutorial Next Tutorial »

In our last tutorial, we created a form that allows a user to request a password reset from the API, which generates a code they can use to do so. We still need to handle emailing them the actual code (and then doing something when they click on the link), but first let’s make our app respond to their actions instead of just sitting there looking like it did nothing.

Start by opening up /src/actions/authentication.js. We need an action creator here that we’ll use to clear our “hey, they submitted the form, so show them new text” state, on the off chance they want to see the form again. So below line 13, add this code:

export const passwordResetClear = () => ({ type: 'AUTHENTICATION_PASSWORD_RESET_CLEAR' });

We also want to actually trigger an error box if they submit a bad email address (though again, to discourage hackers from just trying emails till they get a match, we don’t want our error to actually specifically say that’s the problem). This means we need to adjust our action creator on line 16, like this:

export const passwordResetHashFailure = error => ({ type: 'AUTHENTICATION_PASSWORD_RESET_HASH_FAILURE', error });

We’re going to be potentially bringing up an error box, now, so let’s make sure this routine clears it when it runs. Let’s also trigger our spinner so people know the site’s doing something. Below line 51, add this code:

    // clear the error box if it's displayed
    dispatch(clearError());

    // turn on spinner
    dispatch(incrementProgress());

And then below line 84, add a padding line, and the following:

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

We also need to fix our createHash function so that it’s looking for the proper JSON response. Right now it’s searching the JSON for username on line 79 … but we’re not returning a username. We’re just returning a success boolean, so let’s check for that. Change line 79 to this:

      if (json.success) {

Also if, like me, you happen to have two blank lines between the end of this function and the comment at the top of the logUserIn function, delete that. You’re done with this file. Save it. Now we need to listen for the actions we’re passing via Redux’s dispatch function and actually do something when they occur, so open /src/reducers/authentication.js

We’re going to do something similar to what we did with our registration page, except instead of forwarding the user to an entirely new page, we’re just going to render different text depending on whether they’ve submitted the form or not. To that end, we need a variable to toggle, so below line 5, add the following:

  isPasswordReset: false,

Then head to line 39, and below it, add two new cases: one for the clear and failure actions, and one for the hash created action. Here’s the code:

    case 'AUTHENTICATION_PASSWORD_RESET_CLEAR':
    case 'AUTHENTICATION_PASSWORD_RESET_HASH_FAILURE': {
      const newState = Object.assign({}, state);
      newState.isPasswordReset = false;
      return newState;
    }
    case 'AUTHENTICATION_PASSWORD_RESET_HASH_CREATED': {
      const newState = Object.assign({}, state);
      newState.isPasswordReset = true;
      return newState;
    }

That’s it for this one, but we also want to throw an error when a failure happens, so save this file and then open up /src/reducers/error.js. This one’s easy. We’re just adding another case line to the first big block in the switch statement, so remove the opening brace from line 10 (which might be line 9 if you don’t have a space between your initialState object and your export line … which you should, so if you don’t have one, put it in!) and then below it, add this code:

    case 'AUTHENTICATION_PASSWORD_RESET_HASH_FAILURE': {

There we go. Save the file, and let’s bounce to /src/components/account/ResetPasswordPageContainer.jsx. Let’s start by importing our new clearing function from our action file, so change line 3 to this:

import { createHash, passwordResetClear} from '../../actions/authentication';

Now we need a class method that will use that function, so underneath line 13, add a padding line and then the following code:

  clearPasswordResetFunction() {
    const { dispatch } = this.props;
    dispatch(passwordResetClear());
  }

That method uses this, so we need to bind it. Under line 11 (the comment line), add this code:

    this.clearPasswordResetFunction = this.clearPasswordResetFunction.bind(this);

We need to get our isPasswordReset boolean from the Store, so underneath line 31, add a padding line and declare a quick mapStateToProps function like this:

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

and then change line 35, our export line, to include it, like this:

export default connect(mapStateToProps)(ResetPasswordPageContainer);

Finally, we need to pass a few more props down to ResetPasswordPage, so under line 26, snag isPasswordReset from this.props like this:

    const { isPasswordReset } = this.props.authentication;

And then change line 29 to the following:

      <ResetPasswordPage
        clearPasswordResetFunction={this.clearPasswordResetFunction}
        isPasswordReset={isPasswordReset}
        resetPasswordFunction={this.resetPasswordRequest}
      />

Great! That’s all we need here. Save the file, and open up /src/components/account/ ResetPasswordPage.jsx. Need to make a couple of tweaks here, too. For starters, let’s clear out the email address box if the component re-renders, which it will only do if a submission was successful. The easiest way to do this is with a handy React lifecycle method, componentWillReceiveProps, which runs before the render and lets you see what props are coming in. Below line 18, add a spacing line and then the following:

  // clear out the email form if we're rendering the success message
  componentWillReceiveProps(nextProps) {
    const { isPasswordReset } = nextProps;
    if (isPasswordReset) {
      this.setState({ email: '' });
    }
  }

Now we need a helper function for clearing the password reset status, so add a spacing line underneath our new function (which should end on line 26), and then add this code:

  // show the form again so a new email can be sent
  clearPasswordReset(e) {
    e.preventDefault();
    const { clearPasswordResetFunction } = this.props;
    clearPasswordResetFunction();
  }

We need to bind this function, so do that below line 9 like this:

    this.clearPasswordReset = this.clearPasswordReset.bind(this);

Now we need to head for our render block, so we can check whether isPasswordreset is true or false, and display different stuff depending on its state. To that end, add the following below line 55:

    const { isPasswordReset } = this.props;

Then add a padding line, and the following code:

    if (isPasswordReset) {
      return (
        <div className="row justify-content-center">
          <div className="col-10 col-sm-7 col-md-5 col-lg-4">
            <p>
              An email has been sent to the address you provided containing a link to reset
              your password. Please click that link to proceed with setting a new password.
            </p>
            <p>
              <a href="/account/reset-password" onClick={this.clearPasswordReset}>Re-send Email</a>
            </p>
          </div>
        </div>
      );
    }

Clicking the link will run our clearing action creator, which will change the state, which will generate new props and cause the component to re-render, returning us to the form. I’ve been using React for a few years now, and I still love this aspect of how it works. Once you really wrap your head around it, it makes development a joy.

Right. Save this file. Our last little tweak is to add a “forgot your password?” link to the Log-In page, so open /src/components/account/LoginPage.jsx and, below line 3, add this line:

import { Link } from 'react-router-dom';

Then, below line 78, add this here code:

              <span><Link to="/account/reset-password">Forgot your password?</Link></span>

That’s all we need here. Save the file, and let’s test. Swich to a browser, reload your page, and if you’re logged in, log out. Then head for the log-in page, and click our new “forgot password” link. This’ll take us to our reset password form, which we’ve already seen, so that’s not terribly exciting, but let’s go ahead and put in an invalid email address and click the button. Hooray! Now we get an actual error message.

Finally, let’s test with a valid email. As you can see, we now get our new success text, and if we click the link to re-send the email, we’re presented with the form again, properly blanked out. All good!

In the next tutorial, we’ll look into setting up an SMTP server. This will require creating an account with an outside service, because I am not an insane person, and am not going to try to talk you through setting up your own SMTP server. Don’t worry, there are plenty of free options available. We’ll pick one and go through it step by step.

See you then!

« Previous Tutorial Next Tutorial »