Search code examples
javascriptreactjsvalidationonclickonblur

React validation on blur and on button click


I have multistep form and I want to make the inputs required. Validation should be on blur and on click button "next". On blur works as expected but I have problems with validating on button click. That way like below, I don't go to next step, no matter if I fill in the fields or not and I don't know how to solve it.

In one file I have:

import React, { Component } from 'react';
import PersonalDetails from './PersonalDetails';
import CourseDetails from './CourseDetails';

class Form extends Component {
  state = {
    step: 1,
    firstname: '',
    lastname: '',
    isError: {
      firstName: true,
      lastName: true
    },
    errorMessage: {
      firstName: '',
      lastName: ''
    }

  };

  nextStep = () => {
    const { step } = this.state;
    this.setState({
      step: step + 1
    })
  }
  
  handleChange = input => e => {
    this.setState({
      [input]: e.target.value
    })

    if (input === 'firstname') {
      if (this.state.firstname.length >= 1) {
        this.setState({
          isError: {...this.state.isError, firstName: false}
        })
      }
    }

    else if (input === 'lastname') {
      if (this.state.lastname.length >= 1) {
        this.setState({
          isError: {...this.state.isError, lastName: false}
        })
      }
    }
  }

  validateFirstName = () => {
    if (this.state.firstname.length < 2) {
      this.setState({
        errorMessage: {...this.state.errorMessage, firstName: 'Type your first name (at least 2 characters)'},
        isError: {...this.state.isError, firstName: true}
      });
    }
  }

  validateLastName = () => {
    if (this.state.lastname.length < 2) {
      this.setState({
        errorMessage: {...this.state.errorMessage, lastName: 'Type your last name (at least 2 characters)'},
        isError: {...this.state.isError, lastName: true}
      });
    }
  } 

  render() {
    const {
      step,
      firstname,
      lastname,
      errorMessage,
      isError
    } = this.state;
    
    switch(step) {
      case 1: 
        return (
          <PersonalDetails 
            nextStep={this.nextStep}
            handleChange={this.handleChange}
            firstname={firstname}
            lastname={lastname}
            validateFirstName={this.validateFirstName}
            validateLastName={this.validateLastName}
            errorMessage={errorMessage}
            isError={isError}
          />
        )
      case 2:
        return (
          <CourseDetails />
        )

      default: return null
    }
  }
}

export default Form;

and in other file:

class PersonalDetails extends Component {
  continue = e => {
    e.preventDefault(); 
    this.props.validateFirstName();
    this.props.validateLastName();
    if (!this.props.isError.firstName && !this.props.isError.lastName) {
      this.props.nextStep();
    }
  }

  render() {
    const { 
      firstname, 
      lastname, 
      handleChange, 
      validateFirstName,
      validateLastName,
      errorMessage,
      isError
    } = this.props;

    return (
      <div>
        <form>
          <div>
            <div>
              <label htmlFor='first name'>
              First name
              </label>
              <input type='text' value={firstname} name='first name' onChange={handleChange('firstname')} onBlur={validateFirstName} />
              <p>{isError.firstName && errorMessage.firstName}</p>
            </div>

            <div>
              <label htmlFor='last name'>
              Last name
              </label>
              <input type='text' value={lastname} name='last name' onChange={handleChange('lastname')} onBlur={validateLastName} />
              <p>{isError.lastName && errorMessage.lastName}</p>
            </div>
            
          <div>
            <button onClick={this.continue}>Next</button>
          </div>

        </form>
      </div>
    )
  }
}

export default PersonalDetails;

CourseDetails let's assume it's empty page now:

class CourseDetails extends Component {

  render() {

    return (
      <div>
 
      </div>
    )
  }
}

export default CourseDetails;

Solution

  • Since React state updates are asynchronous (they are not applied right away), the props won't change right after you perform the validation. The easiest way would be to return the validation result from your validators like this:

    validateFirstName = () => {
      if (this.state.firstname.length < 2) {
        this.setState({
          errorMessage: {...this.state.errorMessage, firstName: 'Type your first name (at least 2 characters)'},
          isError: {...this.state.isError, firstName: true}
        });
        return false;
      }
      return true;
    }
    
    validateLastName = () => {
      if (this.state.lastname.length < 2) {
        this.setState({
          errorMessage: {...this.state.errorMessage, lastName: 'Type your last name (at least 2 characters)'},
          isError: {...this.state.isError, lastName: true}
        });
        return false;
      }
      return true;
    } 
    
    

    And then you can use it to determine whether you should continue to the next step or not:

    continue = e => {
      e.preventDefault(); 
      const isFirstNameValid = this.props.validateFirstName();
      const isLastNameValid = this.props.validateLastName();
      if (isFirstNameValid && isLastNameValid) {
        this.props.nextStep();
      }
    }