Chance
Chance

Reputation: 1654

Problems catching the new state of errors in a condition

Good evening, my guys, I'm facing a seemingly simple problem, and sorry for my bad English. The handleSubmit method is called when clicking on a save button, so the ternary conditionals check if the fields are within the validation, if not, it has its state changed to true, where error is rendered in the inputs.

Initial state

  nameError: false,
  cpfError: false,
  emailError: false,
  phoneError: false,
  birthdayError: false,
  addressError: false,
  numberError: false,
  districtError: false,
  stateError: false,
  cityError: false,
  cepError: false,

Full Method Submit:

handleSubmit = () => {
  this.state.name.length < 4
    ? this.setState({ nameError: true })
    : this.setState({ nameError: false })
  this.state.cpf.length < 14
    ? this.setState({ cpfError: true })
    : this.setState({ cpfError: false })
  this.state.email.length === 0
    ? this.setState({ emailError: true })
    : this.setState({ emailError: false })
  this.state.phone.length < 15
    ? this.setState({ phoneError: true })
    : this.setState({ phoneError: false })
  this.state.birthday.length < 10
    ? this.setState({ birthdayError: true })
    : this.setState({ birthdayError: false })
  this.state.address.length === 0
    ? this.setState({ addressError: true })
    : this.setState({ addressError: false })
  this.state.number.length === 0
    ? this.setState({ numberError: true })
    : this.setState({ numberError: false })
  this.state.district.length === 0
    ? this.setState({ districtError: true })
    : this.setState({ districtError: false })
  this.state.state.length === 0
    ? this.setState({ stateError: true })
    : this.setState({ stateError: false })
  this.state.city.length === 0
    ? this.setState({ cityError: true })
    : this.setState({ cityError: false })
  this.state.cep.length !== 9
    ? this.setState({ cepError: true })
    : this.setState({ cepError: false })

  if (
    !this.state.nameError &&
    !this.state.cpfError &&
    !this.state.emailError &&
    !this.state.phoneError &&
    !this.state.birthdayError &&
    !this.state.addressError &&
    !this.state.numberError &&
    !this.state.districtError &&
    !this.state.stateError &&
    !this.state.cityError &&
    !this.state.cepError
  ) {
    this.props.updateUser({
      name: this.state.name,
      cpf: this.state.cpf,
      email: this.state.email,
      phone: this.state.phone,
      birthday: this.state.birthday,
      address: this.state.address,
      number: this.state.number,
      district: this.state.district,
      complement: this.state.complement,
      state: this.state.state,
      city: this.state.city,
      cep: this.state.cep,
      avatar: this.state.avatar,
    })
  }
}

The problem is in this section here.

Here the condition does not pick up the new state that was previously set, it just picks up the new state if I click the save button again. Where am I going wrong? Someone could share knowledge. This in a scenario where all fields are empty soon everyone will have their error state changed to true in ternary conditionals, but the state in if is still the old one, is this because the setstate and asynchronous?

if (
  !this.state.nameError &&
  !this.state.cpfError &&
  !this.state.emailError &&
  !this.state.phoneError &&
  !this.state.birthdayError &&
  !this.state.addressError &&
  !this.state.numberError &&
  !this.state.districtError &&
  !this.state.stateError &&
  !this.state.cityError &&
  !this.state.cepError
) {
  this.props.updateUser({
    name: this.state.name,
    cpf: this.state.cpf,
    email: this.state.email,
    phone: this.state.phone,
    birthday: this.state.birthday,
    address: this.state.address,
    number: this.state.number,
    district: this.state.district,
    complement: this.state.complement,
    state: this.state.state,
    city: this.state.city,
    cep: this.state.cep,
    avatar: this.state.avatar,
  })
}

Upvotes: 0

Views: 37

Answers (2)

smashed-potatoes
smashed-potatoes

Reputation: 2222

The issue is that setState is asynchronous but you are checking the state before it has finished updating. Rather than having multiple setState calls for each of your checks, you could refactor it to a single call, and then do the update in the setState callback, e.g.:

// Collect all of your error items
const nameError = this.state.name.length < 4;
const cpfError = this.state.cpf.length < 14;
...

// Set the state with your error values
this.setState({
  nameError,
  cpfError,
  ...
}, () => { // <-- The second parameter of setState is a callback called after it is finished
  if (
    !this.state.nameError &&
    !this.state.cpfError &&
    ...
  ) {
     this.props.updateUser({
       name: this.state.name,
       ...
     });
  }
);

Upvotes: 1

Cat_Enthusiast
Cat_Enthusiast

Reputation: 15688

Yes, the logic you are creating with this.setState() and this.props.updateUser() are asynchronous. What you want is to call this.props.updateUser() after you have updated your state. this.setState() actually comes with a optional secondary argument that you can call after the state has finished updating, which is exactly what you need.

Let me help you out here by helping you reorganize some of your code. Here is the sandbox as well: https://codesandbox.io/s/zxpnk301pl

Also, you really want to avoid continuously updating your state upon each check of the input value. This can lead to really bad performance issues. Better to update your state in just one action.

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
  state = {
    name: '',
    cpfr: '',
    email: '',
    phone: '',
    birthday: '',
    address: '',
    number: '',
    district: '',
    state: '',
    city: '',
    cep: '',
    errors: {},
  }

  handleOnChange = event => {
    this.setState({
      [event.target.name]: event.target.value,
    })
  }

  validateAndSubmit = event => {
    event.preventDefault()
    const {
      name,
      cpfr,
      email,
      phone,
      birthday,
      address,
      number,
      district,
      state,
      city,
      cep,
      errors,
    } = this.state
    name.length < 4 ? (errors.name = true) : delete errors.name
    cpfr.length < 14 ? (errors.cpfr = true) : delete errors.cpfr
    email.length === 0 ? (errors.email = true) : delete errors.email
    phone.length < 15 ? (errors.phone = true) : delete errors.phone
    birthday.length < 10 ? (errors.birthday = true) : delete errors.birthday
    address.length === 0 ? (errors.address = true) : delete errors.address
    number.length === 0 ? (errors.number = true) : delete errors.number
    district.length === 0 ? (errors.district = true): delete errors.district
    state.length === 0 ? (errors.state = true) : delete errors.state
    city.length === 0 ? (errors.city = true) : delete errors.city
    cep.length !== 9 ? (errors.cep = true) : delete errors.cep

    this.setState(
      {
        errors: errors,
      },
      () => {
        //check if there are no errors
        if(Object.keys(this.state.errors).length === 0){
          this.props.updateUser({
            name: this.state.name,
            cpf: this.state.cpf,
            email: this.state.email,
            phone: this.state.phone,
            birthday: this.state.birthday,
            address: this.state.address,
            number: this.state.number,
            district: this.state.district,
            complement: this.state.complement,
            state: this.state.state,
            city: this.state.city,
            cep: this.state.cep,
            avatar: this.state.avatar,
          })
        }
      }
    )
  }

  render() {
    return (
      <div>
        <form onSubmit={this.validateAndSubmit}>
          <input
            name="name"
            value={this.state.name}
            onChange={this.handleOnChange}
          />
          <input
            name="cpfr"
            value={this.state.cpfr}
            onChange={this.handleOnChange}
          />
          <input
            name="email"
            value={this.state.email}
            onChange={this.handleOnChange}
          />
          <input
            name="phone"
            value={this.state.phone}
            onChange={this.handleOnChange}
          />
          <input
            name="birthday"
            value={this.state.birthday}
            onChange={this.handleOnChange}
          />
          <input
            name="address"
            value={this.state.address}
            onChange={this.handleOnChange}
          />
          <input
            name="number"
            value={this.state.number}
            onChange={this.handleOnChange}
          />
          <input
            name="district"
            value={this.state.district}
            onChange={this.handleOnChange}
          />
          <input
            name="state"
            value={this.state.state}
            onChange={this.handleOnChange}
          />
          <input
            name="city"
            value={this.state.city}
            onChange={this.handleOnChange}
          />
          <input
            name="cep"
            value={this.state.cep}
            onChange={this.handleOnChange}
          />
          <button>Submit</button>
        </form>
        You have {Object.keys(this.state.errors).length} errors
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

Upvotes: 1

Related Questions