Search code examples
reactjsformsformikyup

validate if username already exists while typing using Formik and Yup


I have a task from my company where I have to show whether the username is taken or not, I am using formik and yup validation for the checks so for the time being I have added a custom test functionality to yup validation which shows whether the username is taken or not on the click of the submit button, however, my original task was to show the error message dynamically while the user enters the username, it should tell him if the the username is taken or not. I understood that I might have to manipulate the default handleChange of formik, but I’m unable to do so. Any help is highly appreciated!!!

         validationSchema: Yup.object({
            name: Yup.string()
                .min(2, "Mininum 2 characters")
                .max(30, "Maximum 30 characters")
                .required("Your name is required"),
            email: Yup.string()
                .email("Invalid email format")
                .test("email", "This email has already been registered", function (email) {
                        return checkAvailabilityEmail(email);
                })
                .required("Your email is required"),
            username: Yup.string()
                .min(1, "Mininum 1 characters")
                .max(15, "Maximum 15 characters")
                .test("username", "This username has already been taken", function (username) {
                        return checkAvailabilityUsername(username);
                })
                .required("You must enter a username"),

Solution

  • In your validation schema, just add the test function which is found in Yup. Yup also supports async/await as well!

    Anyways, to answer your question, here's my example:

    validationSchema: Yup.object({
            email: Yup.string()
                .min(8, 'Must be at least 8 characters')
                .max(20, 'Must be less  than 20 characters')
                .required('Email is required')
                .test('Unique Email', 'Email already in use', // <- key, message
                    function (value) {
                        return new Promise((resolve, reject) => {
                            axios.get(`http://localhost:8003/api/u/user/${value}/available`)
                                .then((res) => {
                                    resolve(true)
                                })
                                .catch((error) => {
                                    if (error.response.data.content === "The email has already been taken.") {
                                        resolve(false);
                                    }
                                })
                        })
                    }
                ),
        }),
    

    The value of the email is passed and called to the database which returns a response object. If the response object is an error and the error content is matched, it will be resolved to false and display the message, 'Email already in use'.

    EDIT: If you do not want to validate on every keystroke, you can pass in some props, in particular, validateOnChange={false} or ValidateOnBlur={false}. This can help improve performance issues and reduce API calls to your backend!

                <Formik
                  initialValues={{
                    firstName: '',
                    lastName: '',
                    email: '',
                  }}
                  validationSchema={validationSchema}
                  validateOnChange={false} // disable on every keystroke
                  ...
                >
    

    To display your error messages accordingly:

    {(errors.email && touched.email) && <span style={{ color: 'red', fontSize: '0.8rem', marginLeft: '1.5rem' }}>{errors.email}</span>}