Change Password - Part 2

This video is available to view for members only.

Click here to Join!

Already a member?

Login


In this video we are going to cover two of the three issues that arose towards the end of the previous video. Now that our form is submitting on the happy path, we would 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.

Before I get on to the first two points, I want to say a quick word about the third.

The serialised form representation of our error messages is useful, but not pretty.

We will need to do a bit more work to correct map between the errors Symfony returns, and our Redux Form form fields. This is the subject of the next video.

In this video we want to address the first two points, which whilst easier, do reveal two common tasks you will likely want to do throughout your application.

Firstly, when clicking any button, it's nice to show a different bit of text to indicate that button has transitioned to some other state. It's a purely visual thing, but it's a commonly requested thing for front end developers to have to implement, and with our setup, it's not at all hard.

However, there is a potential gotcha here, so we will address it.

Secondly, we likely want to show the user a nice little Toast (a pop-up message) whenever something interesting happens. This might be hey, your form submitted successfully, and your password is now changed.

This could equally be, that all went wrong, try again.

The process shouldn't be all that surprising to you by now: we're going to dispatch an action indicating exactly what just happened, and let other things register their interest in this change.

Updating Button Text On Submit

We did cover something similar to this already, when we displayed a 'spinner' on our button during login.

But in that instance we applied a basic approach that if followed in this instance, would yield a visual bug.

As soon as a user visits our Profile Page, we dispatch an action which starts the process of requesting their profile from our Symfony 3 API. As part of this, we will add a request to our inProgress array:

// /src/reducers/requestReducer.js

import {REQUEST__STARTED, REQUEST__FINISHED} from '../constants/actionTypes';

export default function request(state = {
  sendingRequest: false,
  inProgress: []
}, action) {

  switch (action.type) {

    case REQUEST__STARTED: {
      return Object.assign({}, state, {
        sendingRequest: true,
        inProgress: state.inProgress.concat([action.payload.requestFrom])
      });
    }

    case REQUEST__FINISHED: {

      let stillInProgress = state.inProgress.filter((item) => item !== action.payload.requestFrom);

      return Object.assign({}, state, {
        sendingRequest: stillInProgress.length > 0,
        inProgress: stillInProgress
      });
    }

    default: {
      return state;
    }
  }
}

For our login button, we naively relied on the output of a query to sendingRequest.

We could do this as on our Login Page we had no other requests being triggered.

However, if we were to do the same for our 'Change Password' button text, we would see the alternate text whenever we first loaded the page, as a concurrent request would be in progress to fetch our profile data.

Fiddlesticks.

Anyway, let's cover the change to ChangePasswordForm first, as that's the easiest place to start:

// /src/components/ChangePasswordForm.js

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

      // * snip *

      <Button type="submit"
              size="lg"
              block
              color="success"
      >
        {props.isSubmitting ?
          <span>Updating password...</span>
          :
          <span>Change Password</span>
        }
      </Button>
    </form>
  );
};

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

I have only shown the important parts here, for the full code sample click here.

This is the easy part as our component literally has no idea how we get into any given state, it just accepts the given props and renders appropriately.

We are going to pass in some prop of isSubmitting. It's a required boolean.

We then use a ternary shorthand to say if isSubmitting is true, then return <span>Updating password...</span>, otherwise <span>Change Password</span>.

Each value is wrapped in a span simply because I so often end up adding icons inside these spans, that my default is now to wrap in a span. You can just as easily return a string:

// also valid:
{props.isSubmitting ? 'Updating password...' : Change Password' }

This is a very common pattern in React.

More interesting is how isSubmitting is determined. And as mentioned previously, there are (at least) two ways of determining this value.

As this value needs to be passed in as a prop, where we determine the outcome of isSubmitting must in this case, be in the ProfilePage container.

// /src/containers/ProfilePage.js

  render() {

    const {
      username = '',
      email = ''
    } = this.props.pageState.profile;

    // no good:
    // const isSubmitting = this.props.pageState.request.isSubmitting

    // instead - but this would be better in a selector:
    const isSubmitting = this.props.pageState.request.inProgress.indexOf(REQUESTS.PROFILE__DOCHANGEPASSWORD__SAGA) > -1;

    return (
      <div>
        <ProfileArea username={username} emailAddress={email}/>
        <ChangePasswordForm
          isSubmitting={isSubmitting}
          onSubmit={this.handleChangePassword.bind(this)} />
      </div>
    );
  }

As ever, I am showing the relevant parts only, if you would like to see the full code example, please click here.

As mentioned, if we rely on isSubmitting, we would see our Updating Password... text display on page load. This is because we request the user's profile in the componentDidMount function of this ProfilePage component. isSubmitting is a generic check to see if any requests are in progress. In this case, we need to be a bit more specific.

One of the benefits of our requestReducer is that we are adding each individual type of request to an array (inProgress) for tracking purposes. We could therefore check if the specific type of request we care about - a PROFILE__DOCHANGEPASSWORD__SAGA - is in progress or not.

Whilst I have added this logic directly into the render method for simplicity, I would encourage you to investigate Redux Selectors and / or Reselect, which if covered further, would add an entire extra video to this course, for the sake of retrieving one value.

Ultimately all we do is check if inProgress contains the expected constant. We use indexOf, which is similar to PHP's array_search function. The most notable difference is that array_search would return false if the requested element is not found in the array, whereas JavaScript returns -1... blah! Hence, our check must be if the returned indexOf is greater than -1, then we have found a match.

Handling Success

Currently when our user's update their passwords, they don't see anything to say things went well.

Actually, more accurately, they don't see anything... at all!

Bad times.

My preferred way of dealing with this is to show a little pop-up / Toast notification.

I'm not going to go into implementing that system here, as I have already covered it previously on the blog. It's not difficult by any means - largely copy / paste. I'm happy to add a video here, or address any questions about this in the comments, either on the blog post itself, or here. Either is good.

What I do want to cover is something that may not have been immediately obvious.

When dispatching an action (putting) from a Saga function, we do not have to go to a reducer.

The dispatched action can be watched for by another Saga function. This can create chains of functions, which could be run concurrently, as we shall see when we get to error handling in the next video.

For now though, we just need to watch for a successful password change. We want to define a new function which would then dispatch another action to display a notification. This is much easier than it sounds:

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

export function *doChangePasswordSucceeded(action) {
  yield put({
    type: types.ADD_NOTIFICATION,
    payload: {
      message: action.payload.message,
      level: 'success'
    }
  });

  console.log('doChangePasswordSucceeded - would have added notification!', {
    message: action.payload.message,
  });
}

export function *watchChangePasswordSucceeded() {
  yield *takeLatest(types.CHANGE_PASSWORD__SUCCEEDED, doChangePasswordSucceeded)

I've added the console.log purely to highlight that something would have happened, but as we don't have a notification system set up, we don't actually see anything. These are points of extension for you, either to implement as 'homework', and / or to understand one way of achieving this outcome.

Really however, we are most concerned with what happens when we make a mistake. Either our new passwords don't match up, or our old password was invalid, or some other whacky combo that end user's seem to inevitably achieve... all need to be gracefully handled.

Our data is getting sent verbatim to our Symfony API. We hope that our API will return some structured data that we can directly use. Or if not directly, then kind of directly. Yeah, in our case it's going to be the latter :)


Code For This Course

Get the code for this course.

Code For This Episode

Get the code for this episode.

Share This Episode

If you have found this video helpful, please consider sharing. I really appreciate it.


Episodes in this series

# Title Duration
1 App Walkthrough - User Experience 03:15
2 App Walkthrough - Developer Experience 07:41
3 Development Environment Setup 06:34
4 Login - Part 1 09:15
5 Login - Part 2 07:55
6 Login - Part 3 12:37
7 Login - Part 4 10:22
8 Login - Part 5 08:00
9 Saving Redux State to Local Storage 08:50
10 Logout 10:57
11 Adding an Auth-aware NavBar 14:43
12 Cleanup, Linting, and Login Form Styling 09:58
13 Showing Spinning Icons, Because Why Not? 08:11
14 More Robust Request Tracking 09:07
15 Getting Started Testing With Jest 06:43
16 Testing Request Reducer - Part 1 11:35
17 Testing Request Reducer - Part 2 05:25
18 Testing AuthSaga - Happy Path 09:19
19 Testing AuthSaga - Unhappy Paths 04:38
21 Testing JavaScript's Fetch with Jest - Happy Path 05:15
21 Testing JavaScript's Fetch with Jest - Unhappy Paths 04:35
22 Getting Started with Jest Mocks 08:52
23 Using Webpack Environment Variables in Jest Tests 09:37
24 User Profile Page - Part 1 07:31
25 User Profile Page - Part 2 10:25
26 User Profile Page - Part 3 07:23
27 Change Password - Part 1 10:01
28 Change Password - Part 2 07:59
29 Change Password - Part 3 - Displaying Errors 06:28
30 Change Password - Part 4 - Converting Errors From Symfony to Redux Form 05:39
31 Change Password - Part 5 - Adding More Tests 05:06
32 Change Password - Part 6 - Avoid Blocking, and Wrap Up 06:23
33 Registration - Part 1 08:50
34 Registration - Part 2 06:25
35 Registration - Part 3 05:25