最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React validation on blur and on button click - Stack Overflow

programmeradmin2浏览0评论

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;

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;
Share Improve this question asked Aug 7, 2020 at 17:09 majamaja 1757 silver badges18 bronze badges 4
  • Odd question, but by looking at the code I have a theory. Try filling the fields properly, and click on the button - it should not work at first, then try clicking it again, does it work then ? – Antonio Erdeljac Commented Aug 7, 2020 at 17:15
  • It doesn't work. – maja Commented Aug 7, 2020 at 17:16
  • Within PersonalDetails, at onChange={handleChange(arg)}, can you update this to onChange={() => handleChange(arg)} and let us know? And within Form, you will need to "bind" the handlChange to that ponent for this keyword to reference that function correctly...let us know what happens when done. – MwamiTovi Commented Aug 8, 2020 at 5:39
  • Now I can't type anything in inputs. I tried to bind handleChange in constructor and without constructor (handleChange = this.handleChange.bind(this)). – maja Commented Aug 8, 2020 at 12:24
Add a ment  | 

2 Answers 2

Reset to default 2

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();
  }
}

I would try a different approach. Instead of calling validate functions, and then trying to check for errors right after, I would extract the validation itself, and use it for single responsibility. Take a look at your current code:

continue = e => {
    e.preventDefault(); 
    this.props.validateFirstName(); // You set / clear the errors in Form state
    this.props.validateLastName(); // You set / clear the errors in Form state
    if (!this.props.isError.firstName && !this.props.isError.lastName) { // You expect to have that exact state which you've just updated in 2 lines above
      this.props.nextStep();
    }
  }

this.props.isError will not get immediately updated in your function in which you've just called another 2 functions which update it. That will be noticeable in second call of that function.

Let's try and switch it up like this:

continue = e => {
    e.preventDefault();

    if (firstname.length < 2 || lastname.length < 2) {
      this.props.validateFirstName();
      this.props.validateLastName();
    } else {
      this.props.nextStep();
    }
  }

This approach would require you to add firstname and lastname props as well.

发布评论

评论列表(0)

  1. 暂无评论