diff --git a/.changeset/fair-cameras-jump.md b/.changeset/fair-cameras-jump.md new file mode 100644 index 000000000..8032251e9 --- /dev/null +++ b/.changeset/fair-cameras-jump.md @@ -0,0 +1,5 @@ +--- +'formik': patch +--- + +Remove low-priority validation implementation diff --git a/packages/formik/package.json b/packages/formik/package.json index a1a083a5d..7f3ef14a5 100644 --- a/packages/formik/package.json +++ b/packages/formik/package.json @@ -38,7 +38,6 @@ "lodash": "^4.17.14", "lodash-es": "^4.17.14", "react-fast-compare": "^2.0.1", - "scheduler": "^0.20.1", "tiny-warning": "^1.0.2", "tslib": "^1.10.0" }, @@ -48,7 +47,6 @@ "@types/lodash": "^4.14.119", "@types/react": "^16.9.55", "@types/react-dom": "^16.9.9", - "@types/scheduler": "^0.16.1", "@types/warning": "^3.0.0", "@types/yup": "^0.24.9", "just-debounce-it": "^1.1.0", diff --git a/packages/formik/src/Formik.tsx b/packages/formik/src/Formik.tsx index 288eb7ce4..618953b1c 100755 --- a/packages/formik/src/Formik.tsx +++ b/packages/formik/src/Formik.tsx @@ -28,11 +28,6 @@ import { } from './utils'; import { FormikProvider } from './FormikContext'; import invariant from 'tiny-warning'; -import { - unstable_LowPriority, - unstable_runWithPriority, - unstable_scheduleCallback, -} from 'scheduler'; type FormikMessage = | { type: 'SUBMIT_ATTEMPT' } @@ -46,10 +41,6 @@ type FormikMessage = | { type: 'SET_FIELD_ERROR'; payload: { field: string; value?: string } } | { type: 'SET_TOUCHED'; payload: FormikTouched } | { type: 'SET_ERRORS'; payload: FormikErrors } - | { - type: 'SET_LOW_PRIORITY_ERRORS'; - payload: { values: Values; errors: FormikErrors }; - } | { type: 'SET_STATUS'; payload: any } | { type: 'SET_FORMIK_STATE'; @@ -76,23 +67,6 @@ function formikReducer( } return { ...state, errors: msg.payload }; - case 'SET_LOW_PRIORITY_ERRORS': - if ( - // Low priority validation can occur after high priority validation and - // this will create stale validation results, e.g: - // SET_FIELD_VALUE-------------------SET_LOW_PRIORITY_ERRORS - // SET_FIELD_VALUE-------------------SET_LOW_PRIORITY_ERRORS - // SET_ISVALIDATING-SET_ERRORS-SET_ISVALIDATING - // - // So we want to skip validation results if values are not the same - // anymore. - isEqual(state.errors, msg.payload.errors) || - !isEqual(state.values, msg.payload.values) - ) { - return state; - } - - return { ...state, errors: msg.payload.errors }; case 'SET_STATUS': return { ...state, status: msg.payload }; case 'SET_ISSUBMITTING': @@ -344,46 +318,6 @@ export function useFormik({ ] ); - // Run validations and dispatching the result as low-priority via rAF. - // - // The thinking is that validation as a result of onChange and onBlur - // should never block user input. Note: This method should never be called - // during the submission phase because validation prior to submission - // is actually high-priority since we absolutely need to guarantee the - // form is valid before executing props.onSubmit. - const validateFormWithLowPriority = useEventCallback( - ( - values: Values = state.values - ): Promise> | Promise => { - return (runWithLowPriority(() => { - return runAllValidations(values) - .then(combinedErrors => { - if (!!isMounted.current) { - dispatch({ - type: 'SET_LOW_PRIORITY_ERRORS', - payload: { values, errors: combinedErrors }, - }); - } - return combinedErrors; - }) - .catch(actualException => { - if (process.env.NODE_ENV !== 'production') { - // Users can throw during validate, however they have no way of handling their error on touch / blur. In low priority, we need to handle it - console.warn( - `Warning: An unhandled error was caught during low priority validation in `, - actualException - ); - } - }); - // The scheduler package is a transitive dependency installed with React - // If we leave this type as is, scheduler types leak and be exported in the build - // which would require folks to install @types/scheduler if they had - // skipLibCheck: false. Or we'd have to add @types/scheduler as a dep. - // Both are unecessary. - }) as unknown) as Promise> | Promise; - } - ); - // Run all validations methods and update state accordingly const validateFormWithHighPriority = useEventCallback( (values: Values = state.values) => { @@ -406,9 +340,9 @@ export function useFormik({ isMounted.current === true && isEqual(initialValues.current, props.initialValues) ) { - validateFormWithLowPriority(initialValues.current); + validateFormWithHighPriority(initialValues.current); } - }, [validateOnMount, validateFormWithLowPriority]); + }, [validateOnMount, validateFormWithHighPriority]); const resetForm = React.useCallback( (nextState?: Partial>) => { @@ -488,7 +422,7 @@ export function useFormik({ } if (validateOnMount) { - validateFormWithLowPriority(initialValues.current); + validateFormWithHighPriority(initialValues.current); } } }, [ @@ -496,7 +430,7 @@ export function useFormik({ props.initialValues, resetForm, validateOnMount, - validateFormWithLowPriority, + validateFormWithHighPriority, ]); React.useEffect(() => { @@ -606,7 +540,7 @@ export function useFormik({ const willValidate = shouldValidate === undefined ? validateOnBlur : shouldValidate; return willValidate - ? validateFormWithLowPriority(state.values) + ? validateFormWithHighPriority(state.values) : Promise.resolve(); } ); @@ -623,7 +557,7 @@ export function useFormik({ const willValidate = shouldValidate === undefined ? validateOnChange : shouldValidate; return willValidate - ? validateFormWithLowPriority(resolvedValues) + ? validateFormWithHighPriority(resolvedValues) : Promise.resolve(); } ); @@ -650,7 +584,7 @@ export function useFormik({ const willValidate = shouldValidate === undefined ? validateOnChange : shouldValidate; return willValidate - ? validateFormWithLowPriority(setIn(state.values, field, value)) + ? validateFormWithHighPriority(setIn(state.values, field, value)) : Promise.resolve(); } ); @@ -899,7 +833,6 @@ export function useFormik({ const imperativeMethods: FormikHelpers = { resetForm, - validateForm: validateFormWithHighPriority, validateField, setErrors, @@ -1195,15 +1128,6 @@ function arrayMerge(target: any[], source: any[], options: any): any[] { return destination; } -/** - * Schedule function as low priority by the scheduler API - */ -function runWithLowPriority(fn: () => any) { - return unstable_runWithPriority(unstable_LowPriority, () => - unstable_scheduleCallback(unstable_LowPriority, fn) - ); -} - /** Return multi select values based on an array of options */ function getSelectedValues(options: any[]) { return Array.from(options) diff --git a/packages/formik/test/Formik.test.tsx b/packages/formik/test/Formik.test.tsx index 2ddaa6e98..7d13ba81e 100644 --- a/packages/formik/test/Formik.test.tsx +++ b/packages/formik/test/Formik.test.tsx @@ -1450,180 +1450,4 @@ describe('', () => { expect(innerRef.current).toEqual(getProps()); }); - - describe('low priority validation', () => { - function renderForm(props?: Partial>) { - const validate = jest.fn(({ name }) => - name == 'ian' ? {} : { name: 'no' } - ); - const renderedErrors: Array<[string, string]> = []; - - function createForm(props?: Partial>) { - return ( - - {({ values, errors, handleBlur, handleChange, handleSubmit }) => { - if (errors.name) { - renderedErrors.push([values.name, errors.name]); - } - - return ( -
- -
- ); - }} -
- ); - } - - const api = render(createForm(props)); - - function rerender(overrides?: Partial>) { - api.rerender(createForm({ ...props, ...overrides })); - } - - return { ...api, rerender, validate, renderedErrors }; - } - - it('bails low priority validations on mount', async () => { - const { getByRole, validate, renderedErrors } = renderForm({ - validateOnMount: true, - }); - - expect(validate).not.toBeCalled(); - - act(() => { - fireEvent.change(getByRole('textbox'), { - persist: noop, - target: { name: 'name', value: 'ian' }, - }); - }); - - expect(validate).not.toBeCalled(); - expect(renderedErrors).toHaveLength(0); - - act(() => { - fireEvent.submit(getByRole('form')); - }); - - expect(renderedErrors).toHaveLength(0); - expect(validate).toBeCalledTimes(1); - expect(validate).lastCalledWith({ name: 'ian' }, undefined); - - await waitFor(() => { - expect(validate).toBeCalledTimes(3); - expect(validate.mock.calls).toEqual([ - // Triggered by submit - [{ name: 'ian' }, undefined], - // Scheduled on first mount - [{ name: '' }, undefined], - // Scheduled on by change - [{ name: 'ian' }, undefined], - ]); - }); - - expect(renderedErrors).toHaveLength(0); - }); - - it('bails low priority validations on reinitialize', async () => { - const { getByRole, validate, renderedErrors, rerender } = renderForm({ - validateOnMount: true, - enableReinitialize: true, - }); - - expect(validate).not.toBeCalled(); - - rerender({ initialValues: { name: 'ian' } }); - - 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 mount - [{ name: '' }, undefined], - // Scheduled on second mount - [{ name: 'ian' }, undefined], - ]); - }); - - expect(renderedErrors).toHaveLength(0); - }); - - it('bails low priority validations on change', async () => { - const { validate, getByRole, renderedErrors } = renderForm({ - initialValues: { name: '' }, - }); - - expect(validate).not.toBeCalled(); - - act(() => { - fireEvent.change(getByRole('textbox'), { - persist: noop, - target: { name: 'name', value: 'i' }, - }); - }); - - act(() => { - fireEvent.change(getByRole('textbox'), { - persist: noop, - target: { name: 'name', value: 'ia' }, - }); - }); - - act(() => { - fireEvent.change(getByRole('textbox'), { - persist: noop, - target: { name: 'name', value: 'ian' }, - }); - }); - - 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(4); - expect(validate.mock.calls).toEqual([ - // Triggered by submit - [{ name: 'ian' }, undefined], - // Scheduled on first change - [{ name: 'i' }, undefined], - // Scheduled on second change - [{ name: 'ia' }, undefined], - // Scheduled on third change - [{ name: 'ian' }, undefined], - ]); - }); - - expect(renderedErrors).toHaveLength(0); - }); - }); }); diff --git a/yarn.lock b/yarn.lock index 846e81dfd..de5c8a713 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3108,11 +3108,6 @@ dependencies: "@types/node" "*" -"@types/scheduler@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" - integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== - "@types/semver@^6.0.0": version "6.2.2" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.2.2.tgz#5c27df09ca39e3c9beb4fae6b95f4d71426df0a9"