« Previous Tutorial Next Tutorial »

When last we left off, we had a form for users to add a new password, but the form didn’t actually do anything. This seems like a suboptimal user experience, so let’s go ahead and fix that. We’re also going to fix a minor error in the API routine we wrote that would cause the API to hang indefinitely if a bad hash was used. Whoops!

Let’s do that first. Open up /routes/api/authentication.js. Head for line 110, which closes an if block. We need to add an else here. So change the line to look like this:

    } else {

and below it add the following two lines:

      result = res.send(JSON.stringify({ error: 'Reset hash not found in database.' }));
    }

Then remove the comment line on 114, which isn’t really necessary since our error messages are pretty explicity, and change the wording on what is now line 114 to look like this:

    result = res.send(JSON.stringify({ error: 'There was an error connecting to the database.' }));

That’s all we need to do here. Save the file, and open up /src/actions/authentication.js. We need to add a couple of action creators, and a function to talk to the API. Below line 16, add the following lines:

export const passwordSaveFailure = error => ({ type: 'AUTHENTICATION_PASSWORD_SAVE_FAILURE', error });
export const passwordSaveSuccess = () => ({ type: 'AUTHENTICATION_PASSWORD_SAVE_SUCCESS' });

With that done, head for the bottom of the file. Our savePassword function looks a lot like the other functions in this file that talk to the API, so I’m going to hand it to you all at once. Here’s the code:

// Save a user's password
export function savePassword(data) {
  return async (dispatch) => {
    // clear the error box if it's displayed
    dispatch(clearError());

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

    // contact the API
    await fetch(
      // where to contact
      '/api/authentication/savepassword',
      // what to send
      {
        method: 'POST',
        body: JSON.stringify(data),
        headers: {
          'Content-Type': 'application/json',
        },
        credentials: 'same-origin',
      },
    )
    .then((response) => {
      if (response.status === 200) {
        return response.json();
      }
      return null;
    })
    .then(async (json) => {
      if (json && json.success) {
        dispatch(passwordSaveSuccess());
      } else {
        dispatch(passwordSaveFailure(new Error(json.error.message ? 'There was an error saving the password. Please try again' : json.error)));
      }
    })
    .catch((error) => {
      dispatch(passwordSaveFailure(new Error(error.message || 'There was an error saving the password. Please try again.')));
    });

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

This should feel pretty straightforward at this point. We send data to our API endpoint and then dispatch actions based on whether we receive a success message or a failure.

Save this file, and let’s head for /src/components/account/ChangePasswordPageContainer.jsx. Now that we have a function for saving the password, let’s import it just below line 2, like this:

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

Now we need to use that function, so let’s add a helper method. Add a padding line below line 10, and then the following code:

  sendPassword(formData) {
    const { dispatch } = this.props;
    const data = {
      hash: this.props.match.params.hash,
      password: formData.password,
    };
    dispatch(savePassword(data));
  }

We need to bind this function, so go back up to line 9 and below it add a padding line and the following code:

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

Previously we were sending the hash down to SavePasswordPage as a prop, but we don’t really need to do that since we’d just have to send it back up on form submission. Our new helper method takes care of that already, but we do need to send that down as a prop, so we can change line 27 to the following:

      <ChangePasswordPage sendPasswordFunction={this.sendPassword} />

And we can delete line 25 entirely. Don’t need it. Once you’ve done that, we’re done with this file. Save it, and open up /src/components/account/ChangePasswordPage.jsx. Let’s use the new function we’re passing down. This is a two-line addition. Just below line 36, delete the comment line and add the following code:

    const { sendPasswordFunction } = this.props;
    sendPasswordFunction(formData);

That’s it! I love when things are easy. Save this file, and let’s make sure we handle any errors that are getting thrown. Open up /src/reducers/error.js. This is another easy change. Remove the opening brace from line 11, and directly below it, add this line:

    case 'AUTHENTICATION_PASSWORD_SAVE_FAILURE': {

That’s all there is to it. Save the file. We now have everything we need to save a user’s new password. What we’re lacking is any way of informing the user that we’ve done so. Writing the system to fix that will take us quite a bit over the five minute mark, so let’s do some testing in this tutorial and then we’ll wrap things up in the next tutorial.

Head for a browser. If you still have your latest reset password hash email, you can navigate directly to localhost:3000/account/change-password/whateverthehashwas. If you don’t have the email, you can either look up your reset hash in the MongoDB console with db.users.find() or you can send yourself a new reset password email.

Once you’re at the change password page with the correct hash, you should be able to choose a new password and successfully submit it. You’ll see your password save success action fire off, but you’ll still be staring at the form for now. That’s OK. Head for the log-in page (log out first if you’re already logged in, obviously), and then try logging in with your new password. It should work!

That’s it for today. In the next tutorial we’ll wrap things up by making it clear to the user what’s going on, which is always a plus. After that, we’ll be done with /account for a while and will be moving into the “music” part of MusicList. Pretty exciting, huh?

See you next time!

« Previous Tutorial Next Tutorial »