Kirk Snyder Jr.
Kirk Snyder Jr.

Reputation: 11

How can I confirm matching values in a React Form with Yup Validation?

I'm trying to validate a confirm password value with yup. Every solution I've looked through points to the same line of code but it's not working as intended.

I have a form with two inputs (password and confirm), and one submit button. The submit button is disabled until the formValues state passes validation. When the passwords don't match, the submit button is correctly disabled and when they match the button becomes active. However the error message does not go away.

Here is my yup schema:

import * as yup from 'yup'

export default yup.object().shape({
  password: yup
    .string()
    .required("Password required"),
  confirm: yup
    .string()
    .oneOf([yup.ref("password"), null], "Passwords don't match")
})

and here is the code for the form itself:

const initialValue = {
  password: "",
  confirm: ""
}

export default function Form() {
const [formValues, setFormValues] = useState(initialValue)
  const [formErrors, setFormErrors] = useState(initialValue)
  const [disabled, setDisabled] = useState(true)

  const onChange = (e) => {
    const {name, value} = e.target;
    yup
      .reach(formSchema, name)
      .validate(value)
      .then(() => {
        setFormErrors({ ...formErrors, [name]: '' })
      })
      .catch((err) => {
        setFormErrors({ ...formErrors, [name]: err.errors[0] })
      })
    
    setFormValues({...formValues, [name]: value })
  }

  const onSubmit = (e) => {
    e.preventDefault()
  }

  useEffect(() => {
    formSchema.isValid(formValues).then((valid) =>
    setDisabled(!valid))
  }, [formValues])

  return (
    <div>
      <form onSubmit={onSubmit}>
        <label>
          Password:
          <input
            type="text"
            name="password"
            onChange={onChange}
            value={formValues.password}
          />
        </label>
        <label>
          Confirm Password:
          <input
            type="text"
            name="confirm"
            onChange={onChange}
            value={formValues.confirm}
          />
        </label>
        <button type="submit" disabled={disabled}>
          Submit
        </button>

        <div className="errors">
          <div>{formErrors.password}</div>
          <div>{formErrors.confirm}</div>
        </div>
      </form>
    </div>
  )
}

Upvotes: 0

Views: 5178

Answers (3)

GM Taium Ahmed
GM Taium Ahmed

Reputation: 107

for typescript

changepassword: Yup.string().oneOf([Yup.ref('password')], 'Passwords must match').required('changepassword is required')

Upvotes: 0

Akinlade Solomon
Akinlade Solomon

Reputation: 31

I added required() at the end of the second password argument and it work.

confirm: yup.string().oneOf([yup.ref("password"), null], "Passwords don't match") }).required()

Upvotes: 2

Navand
Navand

Reputation: 476

First of all each time you checked old formValues. Second you should check touched fields for show error messages after user effected them. I suggest to use Formik.

But I fixed your code and also you can check it on this

import React, { useState, useEffect } from "react";
import * as yup from "yup";

const initialValue = {
  password: "",
  confirm: ""
};

export default function Form() {
  const [formValues, setFormValues] = useState(initialValue);
  const [formErrors, setFormErrors] = useState(initialValue);
  const [touched, setTouched] = useState({password: false, confirm: false});
  const [disabled, setDisabled] = useState(true);

  const formSchema = yup.object().shape({
    password: yup.string().required("Password required"),
    confirm: yup
      .string()
      .oneOf([yup.ref("password"), null], "Passwords don't match")
  });

  const onChange = (e) => {
    const { name, value } = e.target;

    setFormValues({ ...formValues, [name]: value });
    setTouched({...touched, [name]: true});
  };

  const onSubmit = (e) => {
    e.preventDefault();
  };

  useEffect(() => {
    yup
      .reach(formSchema)
      .validate(formValues, { abortEarly: false })
      .then(() => {
        setFormErrors({});
      })
      .catch((err) => {
        const errors = {};
        err.inner.forEach(error => {
          if(touched[error.path]) errors[error.path] = error.message;
        })
        setFormErrors(errors);
      });

    formSchema.isValid(formValues).then(valid => setDisabled(!valid));
  }, [formValues]);

  return (
    <div>
      <form onSubmit={onSubmit}>
        <label>
          Password:
          <input
            type="text"
            name="password"
            onChange={onChange}
            value={formValues.password}
          />
        </label>
        <label>
          Confirm Password:
          <input
            type="text"
            name="confirm"
            onChange={onChange}
            value={formValues.confirm}
          />
        </label>
        <button type="submit" disabled={disabled}>
          Submit
        </button>

        <div className="errors">
          <div>{touched.password && formErrors.password}</div>
          <div>{touched.confirm && formErrors.confirm}</div>
        </div>
      </form>
    </div>
  );
}

Upvotes: 0

Related Questions