formik icon indicating copy to clipboard operation
formik copied to clipboard

No error for custom yup validation using .test method

Open apieceofbart opened this issue 5 years ago • 15 comments

🐛 Bug report

Current Behavior

Formik doesn't throw any error when my custom validation fails. You can see that yup validation works fine when triggered manually however formik doesn't show anything in errors object.

Expected behavior

Formik "errors" should be populated with the custom error.

Reproducible example

https://codesandbox.io/s/formik-example-using-when-test-of-yup-47ds6

Your environment

Software Version(s)
Formik 2.08
React 16.12
TypeScript none
Browser chrome
npm/Yarn
Operating System

apieceofbart avatar Dec 22 '19 13:12 apieceofbart

I found a workaround which is to create a fake field on the schema and use yups test method there. You can get access to other fields by using this.parent inside test callback. Here's an example: https://codesandbox.io/s/formik-example-using-when-test-of-yup-ni2yt

I'm not sure how can it be handled inside formik - it would probably require introducing another property (like customErrors) for this case.

apieceofbart avatar Dec 23 '19 09:12 apieceofbart

Just happened to me: I need it to check for a field that is on the root of the object to validate a deeply nested field. While test() returns false on invalid input, no error is displayed :/

pradej avatar Jan 31 '20 10:01 pradej

I ran into the same issue. I noticed that adding a test in the root object causes the error to not be thrown by Formik.

Tracking down the source, I noticed that this behaviour is caused by the error path being empty, as stated in this function: https://github.com/jaredpalmer/formik/blob/f117c04738ed218b5eb8916d7189e0849962d50d/packages/formik/src/Formik.tsx#L1061-L1073

According to yup, the ValidationError path is empty at root level:

path: a string, indicating where there error was thrown. path is empty at the root level.

At this point I'm not sure whether this is intended or not

samuelpetroline avatar Feb 07 '20 21:02 samuelpetroline

Here the same, yup is working fine, it fills the error object after unsuccessful .test() validation but Formik doesn't display it underneath my customField. Someone solved that problem ?

DeszczMikolaj avatar Apr 29 '20 11:04 DeszczMikolaj

@DeszczMikolaj perhaps my workaround would help you: https://codesandbox.io/s/formik-example-using-when-test-of-yup-ni2yt

apieceofbart avatar Apr 29 '20 16:04 apieceofbart

I've also encountered this, and can add that this is a degradation from v1.x.x, worked fine there

Faithfinder avatar May 20 '20 04:05 Faithfinder

Apologies for the noise, but I don't want this to be marked stale. This is still an issue in 2.1.4

atdrago avatar Jun 26 '20 12:06 atdrago

Having this same issue. Any workarounds yet?

Alebron23 avatar Aug 17 '20 22:08 Alebron23

Like @samuelpetroline I've also noticed that the unset path is the problem. I've worked around this by setting a path in the custom test function by explicitly creating an error. Yup docs for custom test functions show how to do it: https://github.com/jquense/yup#mixedtestname-string-message-string--function-test-function-schema

My code now looks like this:

yup
.object()
.shape({
  field1: yup.string(),
  field2: yup.string(),
})
.test({
  name: "atLeastOneRequired",
  test: function(values) {
    const isValid = ["field1", "field2"].some(field => isNotEmpty(values[field]));

    if(isValid) return true;
    return this.createError({
      path: "field1 | field2",
      message: "One field must be set",
    })
  }
})

Important is to use a non arrow function for test: to have this set correctly. For path any string will do, even with path: "" Formik will be able to recognize it as a validation error.

p.s. in case you're wondering how to test for the path being set with jest, this is how:

expect(schema.validate(values)).rejects.toMatchObject({
  path: expect.stringMatching(/.*/),
});

queltos avatar Nov 02 '20 18:11 queltos

Important is to use a non arrow function for test: to have this set correctly

Or just pass context as second argument via arrow function and call createError on that. This is more type-safe than an anonymous function

lysenkoph-mms avatar Apr 04 '22 06:04 lysenkoph-mms

Using version 2.2.9 and also facing this issue 😩

My code looks like:

  referralCode: yup.string().test({
    name: 'is-valid',
    message: 'This is not a valid referral code',
    async test(value, ctx) {
      console.log(`Validating: ${value}`);
      if (!value) return true;
      const isValid = await verifyReferralCode(value);
      if (isValid) return true;
      console.error(`"${value}" is not a valid referral code`);
      return ctx.createError({
        message: `"${value}" is not a valid referral code`,
      });
    },
  }),

the code runs find, as the console statements run. but the form is not showing the error message 😕

Update: this custom function helped me resolve this https://github.com/jaredpalmer/formik/pull/2902#issuecomment-922492137

jahirfiquitiva avatar Sep 21 '22 03:09 jahirfiquitiva

Using default value before chaining test helped me. yup.mixed().default({}).test(...)

azashi avatar Feb 14 '23 10:02 azashi

In case you're in my position, your custom test is working but you didn't include a <ErrorMessage /> component in your form.

First, check to make sure your yup test is actually failing or not by sticking this line of code inside your Formik render:

<pre>{JSON.stringify(errors, null, 2)}</pre>

Like so:

<Formik
  // ...props
  >
    {({ values, errors }) => (
      <Form>
         <pre>{JSON.stringify(errors, null, 2)}</pre>
         // ... rest of input fields
      </Form>
    )}
  </Formik>

If your errors are appearing on the screen then you simply need to provide the correct path inside your error message component like this:

<ErrorMessage name="<name of your input field>" >

Hope this helps 👍🏾

kharithomas avatar Feb 16 '23 02:02 kharithomas

I was able to solve it using <ErrorMessage /> from Formik

import { Field, ErrorMessage } from 'formik';

return (
  <div className={is_scheduled ? 'block' : 'hidden'}>
    <label htmlFor="schedule_date">
     Date
    </label>
    <Field id="schedule_date" name="schedule_date" type="datetime-local" />
    <ErrorMessage name="schedule_date" />
  </div>
)

Here's the schema

const validationSchema = object({
  is_scheduled: boolean().required(),
  schedule_date: date().test(
    'future',
    'must be future date',
    (val, ctx) => {
      const selectedDate = dayjs(val);
      const now = dayjs(new Date());

      if (selectedDate.isAfter(now)) {
        return true;
      }

      return ctx.createError({
        message: 'must be future date.',
      });
    }
  ),
});

Hope this helps

zzzej avatar Mar 14 '23 03:03 zzzej

What if i want to do this validation? the createError is not working whit this path general.

export const FormSchema = yup
  .object()
  .shape({
    source: yup.string().required(),
    featureType: yup.string().required(),
    file: yup.mixed().when('source', {
      is: 'file',
      then: (schema) => schema.required(),
    }),
    table: yup.object({
      data: yup.array().of(
        yup.object({
          timestamp: yup.date().required(),
          mmsi: yup.string(),
          ircs: yup.string(),
          extMarking: yup.string(),
          name: yup.string(),
          flag: yup.string(),
          type: yup
            .string()
            .oneOf(TypeSelectOptions.map((opt) => opt.value))
            .required(),
          latitude: yup.string(),
          longitude: yup.string(),
          heading: yup.string(),
        })
      ),
    }),
    externalPositions: yup.object({
      active: yup.boolean(),
    }),
  })
  .test({
    name: 'maxMmsi',
    test: function (form, { createError }) {
      if (
        form.externalPositions.active &&
        _.uniqBy(form.table.data, 'mmsi').length > 2
      )
        return createError({
          path: 'general',
          message: 'Importing is limited to a max of 10 distinct vesselss',
        })
      return true
    },
  })

jaure96 avatar Sep 06 '23 10:09 jaure96