diff --git a/.changeset/hungry-seals-bathe.md b/.changeset/hungry-seals-bathe.md
new file mode 100644
index 000000000..270362e21
--- /dev/null
+++ b/.changeset/hungry-seals-bathe.md
@@ -0,0 +1,6 @@
+---
+'formik': patch
+'formik-native': patch
+---
+
+Validate `setFieldTouched` with high priority
diff --git a/app/pages/sign-in.js b/app/pages/sign-in.js
index 5caf2d21a..65a679610 100644
--- a/app/pages/sign-in.js
+++ b/app/pages/sign-in.js
@@ -1,12 +1,16 @@
import React, { useEffect, useState } from 'react';
import { ErrorMessage, Field, Form, FormikProvider, useFormik } from 'formik';
import * as Yup from 'yup';
+import { useRouter } from 'next/router';
const SignIn = () => {
+ const router = useRouter();
const [errorLog, setErrorLog] = useState([]);
const formik = useFormik({
- validateOnMount: true,
+ validateOnMount: router.query.validateOnMount === 'true',
+ validateOnBlur: router.query.validateOnBlur !== 'false',
+ validateOnChange: router.query.validateOnChange !== 'false',
initialValues: { username: '', password: '' },
validationSchema: Yup.object().shape({
username: Yup.string().required('Required'),
@@ -69,6 +73,15 @@ const SignIn = () => {
Submit
+
+
{JSON.stringify(errorLog, null, 2)}
diff --git a/cypress/integration/basic.spec.ts b/cypress/integration/basic.spec.ts
index 618eabfe8..9e119100b 100644
--- a/cypress/integration/basic.spec.ts
+++ b/cypress/integration/basic.spec.ts
@@ -15,7 +15,7 @@ describe('basic validation', () => {
cy.get('#renderCounter').contains('0');
});
- it('should validate show errors on blur', () => {
+ it('should validate show errors on change and blur', () => {
cy.visit('http://localhost:3000/sign-in');
cy.get('input[name="username"]')
@@ -33,6 +33,40 @@ describe('basic validation', () => {
cy.get('#error-log').should('have.text', '[]');
});
+ it('should validate show errors on blur only', () => {
+ cy.visit('http://localhost:3000/sign-in', {
+ qs: {
+ validateOnMount: false,
+ validateOnChange: false,
+ },
+ });
+
+ cy.get('input[name="username"]')
+ .type('john')
+ .blur()
+ .siblings('p')
+ .should('have.length', 0);
+
+ cy.get('input[name="password"]')
+ .type('123')
+ .blur()
+ .siblings('p')
+ .should('have.length', 0);
+
+ cy.get('#error-log').should(
+ 'have.text',
+ JSON.stringify(
+ [
+ // It will quickly flash after `password` blur because `yup` schema
+ // validation is async.
+ { name: 'password', value: '123', error: 'Required' },
+ ],
+ null,
+ 2
+ )
+ );
+ });
+
it('should validate autofill', () => {
// React overrides `input.value` setters, so we have to call
// native input setter
diff --git a/packages/formik/src/Formik.tsx b/packages/formik/src/Formik.tsx
index d9e95ee04..8a5afc9cc 100755
--- a/packages/formik/src/Formik.tsx
+++ b/packages/formik/src/Formik.tsx
@@ -728,7 +728,7 @@ export function useFormik({
const willValidate =
shouldValidate === undefined ? validateOnBlur : shouldValidate;
return willValidate
- ? validateFormWithLowPriority(state.values)
+ ? validateFormWithHighPriority(state.values)
: Promise.resolve();
}
);
diff --git a/packages/formik/test/Formik.test.tsx b/packages/formik/test/Formik.test.tsx
index 1354597f7..2ddaa6e98 100644
--- a/packages/formik/test/Formik.test.tsx
+++ b/packages/formik/test/Formik.test.tsx
@@ -1625,63 +1625,5 @@ describe('', () => {
expect(renderedErrors).toHaveLength(0);
});
-
- it('bails low priority validations on blur', async () => {
- const { validate, getByRole, renderedErrors } = renderForm({
- validateOnChange: false,
- initialValues: { name: '' },
- });
-
- expect(validate).not.toBeCalled();
-
- act(() => {
- fireEvent.change(getByRole('textbox'), {
- persist: noop,
- target: { name: 'name', value: 'i' },
- });
- });
-
- act(() => {
- fireEvent.blur(getByRole('textbox'));
- });
-
- expect(validate).not.toBeCalled();
- expect(renderedErrors).toHaveLength(0);
-
- act(() => {
- fireEvent.change(getByRole('textbox'), {
- persist: noop,
- target: { name: 'name', value: 'ian' },
- });
- });
-
- act(() => {
- fireEvent.blur(getByRole('textbox'));
- });
-
- expect(validate).not.toBeCalled();
- expect(renderedErrors).toHaveLength(0);
-
- act(() => {
- fireEvent.submit(getByRole('form'));
- });
-
- expect(validate).toBeCalledTimes(1);
- expect(renderedErrors).toHaveLength(0);
-
- await waitFor(() => {
- expect(validate).toBeCalledTimes(3);
- expect(validate.mock.calls).toEqual([
- // Triggered by submit
- [{ name: 'ian' }, undefined],
- // Scheduled on first blur
- [{ name: 'i' }, undefined],
- // Scheduled on second blur
- [{ name: 'ian' }, undefined],
- ]);
- });
-
- expect(renderedErrors).toHaveLength(0);
- });
});
});
diff --git a/packages/formik/types/index.d.ts b/packages/formik/types/index.d.ts
index 74ad6734e..75d92d59e 100644
--- a/packages/formik/types/index.d.ts
+++ b/packages/formik/types/index.d.ts
@@ -33,4 +33,33 @@ declare module 'deepmerge' {
function all(objects: Array>, options?: Options): T;
}
}
-declare module 'scheduler';
+
+declare module 'scheduler' {
+ export const unstable_NoPriority = 0;
+ export const unstable_ImmediatePriority = 1;
+ export const unstable_UserBlockingPriority = 2;
+ export const unstable_NormalPriority = 3;
+ export const unstable_LowPriority = 4;
+ export const unstable_IdlePriority = 5;
+
+ export function unstable_runWithPriority(
+ priorityLevel: number,
+ eventHandler: () => T
+ ): T;
+
+ export interface Task {
+ id: number;
+ }
+
+ export interface ScheduleCallbackOptions {
+ delay?: number;
+ }
+
+ export function unstable_scheduleCallback(
+ priorityLevel: number,
+ callback: () => void,
+ options?: ScheduleCallbackOptions
+ ): Task;
+
+ export function unstable_cancelCallback(task: Task): void;
+}