user3470073
user3470073

Reputation: 27

Form validation with multiple if statements on an onClick event

During validation inside handleClick function with multiple if statements, only the last one set the errors state to true (errors.sintomas: true) even leaving blank all the inputs (the remaining ones leave it at false, the initial state of errors, thus preventing the inputs to get a red border expcept for the commented last one). So leaving all the inputs blank gives me an errors state of:

{ mascota: false, propietario: false, fecha: false, hora: false, sintomas: true }

Why is this happening? This is all the code:

const Formulario = () => {
  const [cita, setCita] = useState({
    mascota: '',
    propietario: '',
    fecha: '',
    hora: '',
    sintomas: ''
  });

  const [errors, setErrors] = useState({
    mascota: false,
    propietario: false,
    fecha: false,
    hora: false,
    sintomas: false
  });

  const [triggerAnim, setTriggerAnim] = useState(false);

  const handleChange = (e) => {
    setCita({ ...cita, [e.target.name]: e.target.value });
  };

  const handleClick = (e) => {
    e.preventDefault();
    if (cita.mascota.trim().length < 1) {
      setErrors({ ...errors, mascota: true });
      setTriggerAnim(true);
    }
    if (cita.propietario.trim().length < 1) {
      setErrors({ ...errors, propietario: true });
      setTriggerAnim(true);
    }
    if (cita.fecha.trim().length < 1) {
      setErrors({ ...errors, fecha: true });
      setTriggerAnim(true);
    }
    if (cita.hora.trim().length < 1) {
      setErrors({ ...errors, hora: true });
      setTriggerAnim(true);
    }
    if (cita.sintomas.trim().length < 1) {
      setErrors({ ...errors, sintomas: true });
      setTriggerAnim(true);
    }
  };

  return (
    <Fragment>
      <h2>Crear una cita</h2>
      <form>
        <label htmlFor="mascota">Nombre de tu mascota:</label>
        <input
          type="text"
          name="mascota"
          className={`u-full-width ${errors.mascota ? 'error' : ''}`}
          placeholder="Nombre de la mascota"
          onChange={handleChange}
          value={cita.mascota}
        />
        <label htmlFor=" propietario">Nombre del propietario:</label>
        <input
          type="text"
          name="propietario"
          className={`u-full-width ${errors.propietario ? 'error' : ''}`}
          placeholder="Nombre del propietario"
          onChange={handleChange}
          value={cita.propietario}
        />
        <label htmlFor="fecha">Fecha:</label>
        <input
          type="date"
          name="fecha"
          className={`u-full-width ${errors.fecha ? 'error' : ''}`}
          onChange={handleChange}
          value={cita.fecha}
        />
        <label htmlFor="hora">Hora:</label>
        <input
          type="time"
          name="hora"
          className={`u-full-width ${errors.hora ? 'error' : ''}`}
          onChange={handleChange}
          value={cita.hora}
        />
        <label htmlFor="sintomas">Sintomas</label>
        <input
          type="text"
          name="sintomas"
          className={`u-full-width ${errors.sintomas ? 'error' : ''}`}
          placeholder="Síntomas de la mascota:"
          onChange={handleChange}
          value={cita.sintomas}
        />    
 
        <button className="button-primary u-full-width" onClick={handleClick}>
          Crear Cita
        </button>
      </form>
    </Fragment>
  );
};

export default Formulario;

Don't understand why only the last if statement is working. If I use setErrors(prevState => {return {...prevState, mascota: true}) (the callback with the previous state) then suddenly all works OK. Why using object spread operator to update state does not work?

Upvotes: 1

Views: 883

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 371029

If you click once, the whole handleClick function will also run through its code fully once. Inside that one handleClick, the errors object is constant and is never mutated; it refers to what errors contained during the render before the click.

Say the initial errors object's values were all false the render before click. Then, if the click handler detects a mascota error:

setErrors({ ...errors, mascota: true });

will result in an object with all false properties, except for mascota, whose property is true.

Next, if there's a propietario error as well, the following will run:

setErrors({ ...errors, propietario: true });

which sets the state to the initial errors object (with all false values), combined with a single true property in propietario. The mascota property in the new state will be false, because the errors object that was referenced contained mascota: false, and it was combined with propietario: true. And so on.

You need the callback form so that the new state can be calculated properly even if it was just updated by a prior synchronous call of setErrors, otherwise those prior synchronous calls will not factor into the new state.

Rather than all of this manual validation, you might also consider adding required attributes to the inputs.

Upvotes: 1

Related Questions