formik icon indicating copy to clipboard operation
formik copied to clipboard

Yup schema.validate() options, show every error of a field at the same time

Open acezard opened this issue 7 years ago • 33 comments

Hi, sorry I didn't find another issue related to this problem. And since yup has first class support I thought it would be justified to ask about it.

So I'm trying to set an option, mainly here the abortEarly option. My goal is to have a password field with multiple errors displayed and it seems it was the only way. Couldn't find how to do that with validateSchema()

So I did the following instead:

  validate: (values) => {
    const schema = Yup.object().shape({
      email: Yup.string()
        .matches(/[email protected]/, 'cant change email'),
      providerName: Yup.string()
        .required('type your name'),
      password: Yup.string()
        .min(8, 'at least 8 chars')
        .matches(/[a-z]/, 'at least one lowercase char')
        .matches(/[A-Z]/, 'at least one uppercase char')
        .matches(/[a-zA-Z]+[^a-zA-Z\s]+/, 'at least 1 number or special char (@,!,#, etc).'),
      passwordConfirm: Yup.string()
        .equalTo(Yup.ref('password'), 'passwords don't match')
    })

    return schema.validate(values, { abortEarly: false })
      .then(() => {})
      .catch((err) => {
        throw err
      })
  }

This way I can get every error of a single field and map them to my components to display every error of a field at the same time. Isn't there a cleaner way ? Also I think it could be a good use case to showcase ? Thanks

acezard avatar Nov 09 '17 18:11 acezard

@jaredpalmer I'm also facing this problem. Would you be open to a validationSchemaOptions-property on the Formik-component? If so Id' be willing to provide a PR.

clentfort avatar Jan 29 '18 14:01 clentfort

Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.

stale[bot] avatar Aug 15 '18 17:08 stale[bot]

:/

umpc avatar Aug 15 '18 18:08 umpc

I have the similar problem. I need way to manage yup validation options Now when i use several rules for one field they all called end return last error message.

Tsymalyi avatar Aug 23 '18 12:08 Tsymalyi

I'm also interested in this, I would like to know what are the proposed solutions. I'd like to have something like:

errors = {
  name: [
    "Too short",
    "Cannot contain numbers",
  ]
}

Is it possible?

woile avatar Aug 26 '18 16:08 woile

Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.

stale[bot] avatar Oct 25 '18 16:10 stale[bot]

Comments here keep getting upvotes, so I'm going to post this and hopefully keep this issue open for addressing it in the near future.

umpc avatar Oct 26 '18 00:10 umpc

@jaredpalmer could u help us please?

egemon avatar Dec 27 '18 04:12 egemon

Would love to see this become an option.

DrJacobHolden avatar Mar 05 '19 01:03 DrJacobHolden

New PR for the new version of Formik (which is quite different since it now uses hooks): https://github.com/jaredpalmer/formik/pull/1573

mbrowne avatar May 31 '19 21:05 mbrowne

ght it

I also need to acheive the same, but not able to make it, can you please share the code pen example ? Thanks

tusharpari avatar Jul 01 '19 14:07 tusharpari

This issue is also related: https://github.com/jaredpalmer/formik/issues/750

KrzysztofMadejski avatar Oct 08 '19 12:10 KrzysztofMadejski

@acezard solution don't work for me. Yup returns a ValidationError with packed errors, while Formik expects object with keys.

yupToFormErrors(err) mapping should be called , but it is internal function https://github.com/jaredpalmer/formik/blob/24ac8ddbddc5717c57178b8a9e90f752fbaa6858/src/Formik.tsx#L216-L217

KrzysztofMadejski avatar Oct 08 '19 13:10 KrzysztofMadejski

Agree that we need an option to get a list of all current validation errors at the same time.

dvakatsiienko avatar Mar 04 '20 17:03 dvakatsiienko

Yeah my solution is 3 years old at this point @KrzysztofMadejski, I would not be surprised if it's broken by now.

Sadly it seems this issue has been neglected, but I don't blame the formik owner, open source is open source.

I guess my ticket could be closed maybe, I don't know. Formik has changed a lot since then

acezard avatar Mar 04 '20 19:03 acezard

@acezard This may be late, But there is a property name inner that contain path & error message err.inner.path will return something like user.[property]

usama093 avatar May 16 '20 23:05 usama093

Showing multiple errors at the same time is more friendly. Especially in case of setting a password. Without it, it's bad impression for new users. I really think this need more attention.

@jaredpalmer

deathemperor avatar Aug 24 '20 04:08 deathemperor

Showing multiple errors at the same time is more friendly. Especially in case of setting a password for new users. Without it, it's bad impression for new users. I really think this need more attention.

@jaredpalmer

100%. I have the same situation (create password validation).

dvakatsiienko avatar Aug 24 '20 08:08 dvakatsiienko

Has anyone found any solution to this? I'm having a similar issue. While creating a password, I want to know which options passed and which options failed and display the errors on the UI. Is it possible with Formik and Yup or is there any alternatives?

lamasagar avatar Oct 12 '20 05:10 lamasagar

@jaredpalmer please post a good work around, if any?

uneet7 avatar Oct 16 '20 04:10 uneet7

The solution is to use a custom validate prop for now. We’ll reevaluate in v3

jaredpalmer avatar Oct 16 '20 12:10 jaredpalmer

Weird that my comment got deleted; I guess it's not allowed to mention alternative libraries here. Good news that this feature will be considered for v3 though.

mbrowne avatar Oct 16 '20 12:10 mbrowne

If anyone hits this looking for a stopgap until Formik allows schema options, check this out.

LyricL-Gitster avatar Jan 18 '21 23:01 LyricL-Gitster

I wrote a small function to convert the yup error into an error object:

import { ValidationError } from 'yup';

type ErrorObject = {
  [field: string]: string[];
};
/**
 * Convert yup error into an error object where the keys are the fields and the values are the errors for that field
 * @param {ValidationError} err The yup error to convert
 * @returns {ErrorObject} The error object
 */
export function yupErrorToErrorObject(err: ValidationError): ErrorObject {
  const object: ErrorObject = {};

  err.inner.forEach((x) => {
    if (x.path !== undefined) {
      object[x.path] = x.errors;
    }
  });

  return object;
}

tobiaswaelde avatar Apr 05 '21 01:04 tobiaswaelde

Hello, I used validateOnChange: false, into the useFormik() hook.

Attached the code:

const formik = useFormik({
		initialValues: initialValues(),
		validateOnChange: false,
		validationSchema: Yup.object({
			email: Yup.string().email(t('loginFormWrongEmail')).required(t('loginFormEmailRequired')),
			password: Yup.string().required(t('loginFormPassRequired')),
		}),
		onSubmit: async (formData) => {

			setError('');
			try {
				const { data } = await login({
					variables: {
						input: formData,
					},
				});
				const { token } = data.login;
				setToken(token);
				setUser(decodeToken(token));
			} catch (error) {
				setError(error.message);
			}
		},
	});

dguglielmi-git avatar May 17 '21 15:05 dguglielmi-git

Hi,I need help on One scenario initially, I need submit button to disable so I use the ValidateOnMount it helps me out for the same. The issue is when start typing the text it did not display the error message.

code for the error message

<TextInput style={styles.inputdata} placeholder="Please Enter your Email" placeholderTextColor="#8b9cb5" value={values.email} onChangeText={handleChange('email')} returnKeyType={"next"} onBlur={handleBlur('email')} autoCapitalize="none" />

                            {touched.email && errors.password ?
                                (< Text style={styles.errorText}>{errors.email}</Text>)
                                : null
                            }

Formik

validationSchema={loginValidationSchema} initialValues={{ email: '', password: '' }} validateOnMount={true}

        validateOnChange={false}
        onSubmit={(values,) => {

        }}

can any One Help me out on these

ankitkhaire20 avatar Jun 11 '21 01:06 ankitkhaire20

Step 1: Paste this out of your function const asyncValidateSchema = (schema) => (values) => schema .validate(values, { abortEarly: false, strict: false, }) .then(() => ({})) .catch(({ inner }) => inner.reduce( (memo, { path, message }) => ({ ...memo, [path]: (memo[path] || []).concat(message), }), {} ) ); Step 2: Change Formik validation schema to validate <Formik enableReinitialize={true} onSubmit={(values, actions) => handleSubmit(values, actions) } initialValues={defaultState} validate={asyncValidateSchema(validationSchema)} > Step 3: Now add validation schema outside the function const validationSchema = yup.object().shape({ new_password: yup .string() .required() .min(8, "At least 8 characters") .upperCaseCheck() .numberCheck() .specialCharacterCheck() .lowerCaseCheck(), re_new_password: yup .string() .required() .oneOf( [yup.ref("new_password"), null], "The password you entered does not match" ), }); I have added some validation scenarios for password yup.addMethod(yup.string, "upperCaseCheck", function (errorMessage) { return this.test(oneUpperCase, errorMessage, function (value) { const { path, createError } = this; // change regex here to make other functions if (!/(?=.*[A-Z])/.test(value)) { return createError({ message: At least one uppercase letter, path: path, // Fieldname }); } return true; }); }); Now you will get a list of errors inside errors.new_password. Hope this helps. Cheers!

niketanmoon avatar Sep 21 '21 19:09 niketanmoon

The solution is to use a custom validate prop for now. We’ll reevaluate in v3

Got any examples of that?

lindeberg avatar Oct 25 '21 08:10 lindeberg

const asyncValidateSchema = (schema) => (values) => schema .validate(values, { abortEarly: false, strict: false, }) .then(() => ({})) .catch(({ inner }) => inner.reduce( (memo, { path, message }) => ({ ...memo, [path]: (memo[path] || []).concat(message), }), {} ) );

For anyone interested in using this solution with either TypeScript or with nested properties within formik using the . syntax (i.e. parent_obj.formKey). I ended up utilizing lodash's set function to set those nested keys and ensure that formik can understand the returned errors object, resulting in the following:

function FormikAsyncValidateSchema<FormState extends object>( 
	schema: Yup.ObjectSchema<any> 
): ( values: FormState ) => Promise<FormikErrors<FormState>> {
		return async function ( values: FormState ): Promise<FormikErrors<FormState>> {
			try {
				await schema.validate( values, { abortEarly: false, strict: false, }  );
				return Promise.resolve( {} );
			} catch ( yupErr ) {
				return yupErr.inner.reduce( ( memo, { path, message } ) => {
					return set( memo, path, ( memo[path] || [] ).concat( message ) );
				}, {} );
			}
		};
	}

this results in the returned object looking like this:

const errors = {
	parent_obj: {
		childFormKey: ['Child Form Key is required']
	}
}

instead of:

const errors = {
	"parent_obj.childFormKey": ['Child Form Key is required']
}

uptonm avatar Nov 29 '21 17:11 uptonm

Here's how I addressed:

validate={(values) => {
  // Return all errors at once
  return validationSchema.validate(values, { abortEarly: false })
  .then(() => {})
  .catch((err) => {
    return err.inner.reduce((obj, e) => {
      if (!(e.path in obj)) obj[e.path] = []
      obj[e.path] = obj[e.path].concat(e.errors)
      return obj
    }, {})
  })
}}

hancush avatar Jul 28 '23 20:07 hancush