Refactor client side validations

Linked Projects:

Calreact
 
Lesson code - https://github.com/learnetto/calreact/tree/m6l7-generalised-validations

In the previous lesson, we ensure we've got client side validations working for both our fields, but we're not showing any errors yet. Before we can add those, we need to store the validity of each field separately.

We’re going to refactor this code into a separate validateField method.

Let’s start off by changing the state values of title and appt_time to an object, so that we can store both the value and the validity of that field inside the state.

So we're going to have an object with two keys:
  • Value
  • Valid
We'll set the value to a blank string, and valid to false: 

title: {value: '', valid:false},

Let’s do the same for appt_time:

appt_time: {value: '', valid:false},

So now we’re saving the validity status of each field in the state as well.

We then need to make sure we are using the right key for these values.

Let’s add .value in the code, first at Appointment Title:

<input name='title' placeholder='Appointment Title'
  value={this.props.title.value}
  onChange={this.handleChange} />

and then at Datetime:

<Datetime input={false} open={true} inputProps={inputProps}
  value={moment(this.props.appt_time.value)}
  onChange={this.setApptTime} />

Then I also want to refactor our handleChange function in the AppointmentForm component. Currently we are sending the field name and value as an object. Instead, what we'll do is just send them separately so that we can use them directly without having to parse this object.

So let’s remove obj[name], and then send fieldName and fieldValue to onUserInput:

handleChange = (e) => {
  const fieldName = e.target.name;
  const fieldValue = e.target.value;
  this.props.onUserInput(fieldName, fieldValue);
}

Then let's change that method to accept these values, fieldName, fieldValue:

handleUserInput = (fieldName, fieldValue) => {
  this.setState(obj, this.validateForm);
}

Then we’ll change the way we update the state, so let’s remove  line 21, and update our code:

handleUserInput = (fieldName, fieldValue) => {
  const newFieldState = update(this.state[fieldName],
                                {value: {$set: fieldValue}});
}

Note that this is not going to work just yet because we haven’t called setState yet. Before we do that, let’s first make the same change for the appt_time field.

So in AppointmentForm, let’s change the setApptTime method. Change it to fieldName and then simplify this, and then pass these values to onUserInput:

setApptTime = (e) => {
  const fieldName = 'appt_time';
  const fieldValue = e.toDate();
  this.props.onUserInput(fieldName, fieldValue);
}
 
As a result, I can now select a date in the browser. But when you look at the app in the browser, notice that our form validation doesn’t work because we are not updating the state yet.

Let’s do that:

handleUserInput = (fieldName, fieldvalue) => {
  const newFieldState = update(this.state[fieldName],
                                Pvalue: {$set: fieldValue}});
  this.setState({[fieldName]: newFieldState});
}

This  is a simple way to access the keys title and appt_time in our state object.

Now after setting the state, we need to still validate the form. Do that by passing a new callback function called validateField, where we’re going to validate each field separately:

handleUserInput = (fieldName, fieldvalue) => {
  const newFieldState = update(this.state[fieldName],
                                Pvalue: {$set: fieldValue}});
  this.setState({[fieldName]: newFieldState}, 
                 () => this.validateField(fieldName, fieldValue) });
}

Let’s define it, we’ll pass it the fieldname and fieldvalue.  Let’s declare a variable called fieldValid and then do a switch statement based on the fieldname.

We will refactor this further in a bit to avoid hard coding the field names, but let’s just do this as an intermediate step first.

So for case title, let’s say fieldValid equals. Let’s copy paste this check
[add screenshot]

and then case appt_time, set fieldValid to these two checks:

validateField (fieldName, fieldValue) {
  let fieldValid;
  switch(fieldName) {
    case 'title':
      fieldValid = this.state.title.trim().length > 2;
      break;
    case 'appt_time':
      fieldValid = moment(this.state.appt_time).isValid() &&
                   moment(this.state.appt_time).isAfter()});
    default:
      break;
  }
}

Now we need to update the state. Let's copy paste this bit and change it to valid instead of value.

[screenshot or bit of code]

Then let's call this.setState and  pass it [fieldname]: newFieldState:

validateField (fieldName, fieldValue) {
  let fieldValid;
  switch(fieldName) {
    case 'title':
      fieldValid = this.state.title.trim().length > 2;
      break;
    case 'appt_time':
      fieldValid = moment(this.state.appt_time).isValid() &&
                   moment(this.state.appt_time).isAfter()});
    default:
      break;
  }
  const newFieldState = update(this.state[fieldName],
                                {balid: {$set: fieldValid}});
  this.setState({[fieldName]: newFieldState});
}

Finally we can connect this all up to make our button work again by passing this.validateForm as a callback here:

validateField (fieldName, fieldValue) {
  let fieldValid;
  switch(fieldName) {
    case 'title':
      fieldValid = this.state.title.trim().length > 2;
      break;
    case 'appt_time':
      fieldValid = moment(this.state.appt_time).isValid() &&
                   moment(this.state.appt_time).isAfter()});
    default:
      break;
  }
  const newFieldState = update(this.state[fieldName],
                                {balid: {$set: fieldValid}});
  this.setState({[fieldName]: newFieldState}, this.validateForm);
}

and change that method to use the validity state values, so let's change this to

validateForm () {
  this.setState({formValid: this.state.state.title.valid &&
                            this.state.appt_time.valid
               });
}

So we’re using the state values in here which we set  to decide whether the form is valid or not.

[line 13 / line 40 code: paste in here]

Now refresh the page to see if the form works. As it doesn't, it might mean that we forgot to use the new value somewhere.

In validateField, we need to add .value:

validateField (fieldName, fieldValue) {
  let fieldValid;
  switch(fieldName) {
    case 'title':
      fieldValid = this.state.title.value.trim().length > 2;
      break;
    case 'appt_time':
      fieldValid = moment(this.state.appt_time.value).isValid() &&
                   moment(this.state.appt_time.value).isAfter()});
    default:
      break;
  }
  const newFieldState = update(this.state[fieldName],
                                {balid: {$set: fieldValid}});
  this.setState({[fieldName]: newFieldState}, this.validateForm);
}

With this change, the form works.

The Make Appointments button gets enabled when we enter valid values and gets disabled otherwise.

Liked this tutorial? Get more like this in your inbox