Change Password - Part 1


In this video we are going to get started adding in the functionality to allow a user to change their password. In order to change their password they must supply their current password, and then a new password, along with a repeated new password value to ensure no typos. Standard stuff.

To handle the form side of things, we are going to use Redux Form. This is going to ensure we have our form's state synced up to our Redux store.

Now, the form implementation itself is perhaps the least interesting part of this whole journey, so let's start by outlining the code for the ChangePasswordForm component:

// /src/components/ChangePasswordForm.js

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

const ChangePasswordForm = (props) => {
  return (
    <form  className="form-change-password">

      <Field component="input"
             name="currentPassword"
             type="password"
             placeholder="Current Password"
             required="required"
             className="form-control"
      />

      <Field component="input"
             name="newPassword"
             type="password"
             placeholder="New Password"
             required="required"
             className="form-control"
      />

      <Field component="input"
             name="newPasswordRepeated"
             type="password"
             placeholder="New Password Repeated"
             required="required"
             className="form-control"
      />

      <Button type="submit"
              size="lg"
              block
              color="success"
      >
        Change Password
      </Button>
    </form>
  );

};

ChangePasswordForm.propTypes = {
};

export default reduxForm({
  form: 'change-password'
})(ChangePasswordForm);

We've already touched on Redux Form in this series during our Login journey, so I would point you at those videos if brand new to Redux Form - as there are some gotchas.

As it stands we have three fields to accept our input. Each of these fields is the most basic input component type as per Redux Form (see #3).

Our fields are wrapped in a form, as you would expect. This form will need an onSubmit function which we haven't yet provided.

The Button component comes from Reactstrap. You needn't use Reactstrap, it's personal preference.

I've added a section in for PropTypes, though we currently have none defined.

Finally, we need to wrap this form in a reduxForm component, first passing in the configuration - simply a unique name for our form, and secondly the ChangePasswordForm component which we are wrapping.

At this stage we should have a form which displays, and which will dispatch actions to describe each input change on our form fields whenever input is detected, and ... that's about it. It won't actually do the change password process for us, as we haven't implemented that at all just yet.

Being Interesting On Submit

All the fun of this form happens when the form is submitted.

Somewhat peculiarly when first starting with React is the concept of passing things in to components, rather than declaring them directly on the components themselves.

An excellent example of this is when dealing with a form.

We define the form itself as a component.

Now, our change password form is likely only ever going to change passwords. But some forms may be involved in both creating, and updating data.

The nice - albeit initially confusing - part of using forms in React is that we would pass in the function that is called when the form is submitted to the form as a prop. This means we can use the same basic form for both create, and update, and have worry about the specific implementation details of what to do on create, or update, in different components altogether.

As mentioned though, a change password form is likely only ever going to do just that - change passwords - so this concept seems a little unnecessary in this instance. But I am following the same convention all the same - I just wanted to explain why.

In summary - we have our ProfilePage, which imports and uses the ChangePasswordForm component. We define the function - handleChangePassword inside our ProfilePage, and pass this function into the ChangePasswordForm as the 'thing' to do when the form is submitted (onSubmit).

Let's define this handleChangePassword function:

  handleChangePassword(formData) {
    const {currentPassword, newPassword, newPasswordRepeated} = formData;

    this.props.dispatch({
      type: types.CHANGE_PASSWORD__REQUESTED,
      payload: {
        userId: this.props.pageState.auth.id,
        currentPassword,
        newPassword,
        newPasswordRepeated,
      }
    });
  }

There's really nothing new here.

When the form is submitted, it will call this function (or it will, when we hook it up to do so). Initially we destructure out the form field values (literally the name properties from each of the form fields) from the formData.

Then, we dispatch a new action, and from the perspective of ProfilePage, we are done.

In order to make this work, we need to now pass this function into ChangePasswordForm:

// /src/containers/ProfilePage.js

class ProfilePage extends React.Component {

  handleChangePassword(formData) {
    const {currentPassword, newPassword, newPasswordRepeated} = formData;

    this.props.dispatch({
      type: types.CHANGE_PASSWORD__REQUESTED,
      payload: {
        userId: this.props.pageState.auth.id,
        currentPassword,
        newPassword,
        newPasswordRepeated,
      }
    });
  }

  render() {
    return (
      <div>
        <ChangePasswordForm onSubmit={this.handleChangePassword.bind(this)} />
      </div>
    );
  }
}

Note that I have stripped out all but the relevant parts of this change. For the full code, click here.

The one interesting point here is that we must .bind(this) when passing the handleChangePassword function into the ChangePasswordForm component.

The key point here is that we are passing in the handleChangePassword function, and not immediately calling it. It will be called by ChangePasswordForm at some point in the future - when the user clicks submit.

When the user clicks submit, React cannot figure out what handleChangePassword belonged too. This might not be a problem, until we need to use this inside handleChangePassword. As it happens, we do need this, for accessing this.props.

When our function is called, this will be undefined. Therefore our function will fail, as this.props.dispatchandthis.props.pageStatecannot be found. More technically accurate would simply bethis.propscannot be found, as again,thisitself isundefined`.

Therefore, by binding our function to this, we can ensure that this will point to ProfilePage as expected. This is because .bind will really create a new function for us - behind the scenes - where this is properly set for us.

In researching this (ho ho) further, I found that the best practice is to bind inside the constructor, making this more accurate:

// /src/containers/ProfilePage.js

class ProfilePage extends React.Component {

  constructor(props) {
    super(props);

    this.handleChangePassword = this.handleChangePassword.bind(this);
  }

  handleChangePassword(formData) {
    const {currentPassword, newPassword, newPasswordRepeated} = formData;

    this.props.dispatch({
      type: types.CHANGE_PASSWORD__REQUESTED,
      payload: {
        userId: this.props.pageState.auth.id,
        currentPassword,
        newPassword,
        newPasswordRepeated,
      }
    });
  }

  render() {
    return (
      <div>
        <ChangePasswordForm onSubmit={this.handleChangePassword} />
      </div>
    );
  }
}

Anyway, after all this we still need to update ChangePasswordForm to use this function onSubmit:

// src/components/ChangePasswordForm.js

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

const ChangePasswordForm = (props) => {
  return (
    <form onSubmit={props.handleSubmit} className="form-change-password">

        // * snip* 

    </form>
  );

};

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

export default reduxForm({
  form: 'change-password'
})(ChangePasswordForm);

Easy enough. We've setup the onSubmit event on our form. This form doesn't have any idea what that handleSubmit function is, or even where it comes from. We tell this component via propTypes that it will be a function, and that it isRequired. And that is it.

The keen eyed will have spotted one glaring weirdness. We pass in our prop as onSubmit, but our form onSubmit calls handleSubmit. This is the most confusing part of using Redux form in my opinion, and I wrote more about this particular workflow in this video writeup, so do refer back there if confused.

Change Password Saga Implementation

Now we have a bound function which we are passing in to our form.

On submit of this form, the function will be called.

This function is going to get the form's data, and configure and dispatch a new action.

This action will need to trigger the real API call. This involves side effects, and therefore we need to manage this process via a Saga. We already have profileSaga, so let's just added this new function in there:

// /src/sagas/profile.saga.js

import * as api from '../connectivity/api.profile';
import {call, put} from 'redux-saga/effects';
import {takeLatest} from 'redux-saga';
import * as types from '../constants/actionTypes';

export const REQUESTS = {
  PROFILE__DOREQUESTPROFILE__SAGA: 'profile.doRequestProfile.saga',
  PROFILE__DOCHANGEPASSWORD__SAGA: 'profile.doChangePassword.saga',
};

export function *doChangePassword(action) {

  try {

    const {userId, currentPassword, newPassword, newPasswordRepeated} = action.payload;

    yield put({
      type: types.REQUEST__STARTED,
      payload: {
        requestFrom: REQUESTS.PROFILE__DOCHANGEPASSWORD__SAGA
      }
    });

    const responseBody = yield call(api.changePassword, userId, currentPassword, newPassword, newPasswordRepeated)

    yield put({
      type: types.CHANGE_PASSWORD__SUCCEEDED,
      payload: {
        message: responseBody
      }
    });

  } catch (e) {

    yield put({
      type: types.REQUEST__FAILED,
      payload: {
        message: e.message,
        statusCode: e.statusCode
      }
    });

  } finally {

    yield put({
      type: types.REQUEST__FINISHED,
      payload: {
        requestFrom: REQUESTS.PROFILE__DOCHANGEPASSWORD__SAGA
      }
    });

  }
}

export function *watchChangePassword() {
  yield* takeLatest(types.CHANGE_PASSWORD__REQUESTED, doChangePassword);
}

Again, I have removed irrelevant parts from this code. Click here for the full code sample.

We must also remember to add this new watch function to our rootSaga:

// /src/sagas/index.js

import {fork} from 'redux-saga/effects';
import * as authSaga from './auth.saga';
import * as profileSaga from './profile.saga';

export default function *rootSaga() {
  yield [
    fork(authSaga.watchLogin),
    fork(authSaga.watchLoginSucceeded),
    fork(authSaga.watchLoginFailed),

    fork(authSaga.watchLogoutRequested),

    fork(profileSaga.watchChangePassword), // this line
    fork(profileSaga.watchRequestProfile),
  ];
}

Just like every other saga we have crafted so far, we start with a watch function, watching for our specific action type constant - CHANGE_PASSWORD__REQUESTED.

When actions of this type are seen, we hand off to doChangePassword.

Again, we start by destructuring out the given data from the action.payload.

We start our request process. Nothing new here.

Then we need to make a call to the API to actually do the password change. This isn't a function that we actually have as of yet. But we can look at our Behat feature specifications to understand what we will need to make this work:

// Symfony API project

// /src/AppBundle/Features/password_change.feature#L43

  Scenario: Can change password with valid credentials
    When I am successfully logged in with username: "peter", and password: "testpass"
     And I send a "POST" request to "/password/1/change" with body:
      """
      {
        "current_password": "testpass",
        "plainPassword": {
          "first": "new password",
          "second": "new password"
        }
      }
      """
    Then the response code should be 200
     And the response should contain "The password has been changed"

We're going to need... amazingly, all the data we passed in :) It's as if we had this planned out ahead of time.

Knowing this, let's create the function to enable this API call:

// /src/connectivity/api.profile.js

import asyncFetch from './async-fetch';
import {getBaseRequestConfig} from './baseRequestConfig';

export async function changePassword(userId, currentPassword, newPassword, newPasswordRepeated) {

  const baseRequestConfig = getBaseRequestConfig();

  const requestConfig = Object.assign({}, baseRequestConfig, {
    method: 'POST',
    body: JSON.stringify({
      "current_password": currentPassword,
      "plainPassword": {
        "first": newPassword,
        "second": newPasswordRepeated
      }
    })
  });

  /* global API_BASE_URL */
  const url = API_BASE_URL + '/password/' + userId + '/change';

  const response = await asyncFetch(url, requestConfig);

  return response.json();
}

Again, we've covered all of this already, so if unsure, please refer back.

If all this goes to plan we're going to dispatch another action to say the password change was successful:

    yield put({
      type: types.CHANGE_PASSWORD__SUCCEEDED,
      payload: {
        message: responseBody
      }
    });

At this stage we should be able to successfully update our password.

We would now likely want to:

  • update our button text when the user submits the form;
  • show a notification on successfully changing their password;
  • display the returned errors from the API on the form, should any be sent.

All of these are additional steps which are important. Some are easier to address than others. And we'll get onto that in the very next video.

Code For This Course

Get the code for this course.

Code For This Video

Get the code for this video.

Episodes