Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate setFieldTouched with high priority. #2882

Merged
merged 1 commit into from
Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/hungry-seals-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'formik': patch
'formik-native': patch
---

Validate `setFieldTouched` with high priority
15 changes: 14 additions & 1 deletion app/pages/sign-in.js
Original file line number Diff line number Diff line change
@@ -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'),
Expand Down Expand Up @@ -69,6 +73,15 @@ const SignIn = () => {
Submit
</button>

<button
type="reset"
onClick={() => {
setErrorLog([]);
}}
>
Reset
</button>

<pre id="error-log">{JSON.stringify(errorLog, null, 2)}</pre>
</Form>
</FormikProvider>
Expand Down
36 changes: 35 additions & 1 deletion cypress/integration/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]')
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/formik/src/Formik.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ export function useFormik<Values extends FormikValues = FormikValues>({
const willValidate =
shouldValidate === undefined ? validateOnBlur : shouldValidate;
return willValidate
? validateFormWithLowPriority(state.values)
? validateFormWithHighPriority(state.values)
: Promise.resolve();
}
);
Expand Down
58 changes: 0 additions & 58 deletions packages/formik/test/Formik.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1625,63 +1625,5 @@ describe('<Formik>', () => {

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);
});
});
});
31 changes: 30 additions & 1 deletion packages/formik/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,33 @@ declare module 'deepmerge' {
function all<T>(objects: Array<Partial<T>>, 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<T>(
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;
}