Skip to content

Commit

Permalink
Run low-pri validation at a given field / path in schema (#1578)
Browse files Browse the repository at this point in the history
* Use validateAt for handleChange/handleBlur

* Fix single field validation
  • Loading branch information
jaredpalmer committed Jun 27, 2019
1 parent d249f34 commit 7ba657c
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 62 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"tsdx": "^0.7.2",
"typescript": "^3.5.2",
"webpack": "^4.31.0",
"yup": "0.21.3"
"yup": "^0.27.0"
},
"lint-staged": {
"**/*.{ts,tsx}": [
Expand Down
75 changes: 53 additions & 22 deletions src/Formik.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,28 @@ export function useFormik<Values extends FormikValues = FormikValues>({

const runSingleFieldLevelValidation = React.useCallback(
(field: string, value: void | string): Promise<string> => {
return new Promise(resolve =>
resolve(fieldRegistry.current[field].validate(value))
);
return new Promise(r => r(fieldRegistry.current[field].validate(value)));
},
[]
);

const runFieldLevelValidations = React.useCallback(
(values: Values): Promise<FormikErrors<Values>> => {
(values: Values, field?: string): Promise<FormikErrors<Values>> => {
if (field) {
const hasValidateFn =
fieldRegistry.current[field] &&
isFunction(fieldRegistry.current[field].validate);

if (!hasValidateFn) {
// bail
return Promise.resolve(emptyErrors);
}

return runSingleFieldLevelValidation(field, getIn(values, field)).then(
result => setIn(emptyErrors, field, result)
);
}

const fieldKeysWithValidation: string[] = Object.keys(
fieldRegistry.current
).filter(f => isFunction(fieldRegistry.current[f].validate));
Expand Down Expand Up @@ -274,11 +287,11 @@ export function useFormik<Values extends FormikValues = FormikValues>({

// Run all validations and return the result
const runAllValidations = React.useCallback(
(values: Values) => {
(values: Values, field?: string) => {
return Promise.all([
runFieldLevelValidations(values),
props.validationSchema ? runValidationSchema(values) : {},
props.validate ? runValidateHandler(values) : {},
runFieldLevelValidations(values, field),
props.validationSchema ? runValidationSchema(values, field) : {},
props.validate ? runValidateHandler(values, field) : {},
]).then(([fieldErrors, schemaErrors, validateErrors]) => {
const combinedErrors = deepmerge.all<FormikErrors<Values>>(
[fieldErrors, schemaErrors, validateErrors],
Expand All @@ -304,11 +317,21 @@ export function useFormik<Values extends FormikValues = FormikValues>({
// is actaully high-priority since we absolutely need to guarantee the
// form is valid before executing props.onSubmit.
const validateFormWithLowPriority = useEventCallback(
(values: Values = state.values) => {
(values: Values = state.values, field?: string) => {
return unstable_runWithPriority(LowPriority, () => {
return runAllValidations(values).then(combinedErrors => {
return runAllValidations(values, field).then(combinedErrors => {
if (!!isMounted.current) {
dispatch({ type: 'SET_ERRORS', payload: combinedErrors });
if (field) {
dispatch({
type: 'SET_FIELD_ERROR',
payload: {
field,
value: combinedErrors && (combinedErrors as any)[field],
},
});
} else {
dispatch({ type: 'SET_ERRORS', payload: combinedErrors });
}
}
return combinedErrors;
});
Expand All @@ -319,12 +342,20 @@ export function useFormik<Values extends FormikValues = FormikValues>({

// Run all validations methods and update state accordingly
const validateFormWithHighPriority = useEventCallback(
(values: Values = state.values) => {
(values: Values = state.values, field?: string) => {
dispatch({ type: 'SET_ISVALIDATING', payload: true });
return runAllValidations(values).then(combinedErrors => {
return runAllValidations(values, field).then(combinedErrors => {
if (!!isMounted.current) {
dispatch({ type: 'SET_ISVALIDATING', payload: false });
if (!isEqual(state.errors, combinedErrors)) {
if (field) {
dispatch({
type: 'SET_FIELD_ERROR',
payload: {
field,
value: combinedErrors && (combinedErrors as any)[field],
},
});
} else {
dispatch({ type: 'SET_ERRORS', payload: combinedErrors });
}
}
Expand Down Expand Up @@ -396,14 +427,14 @@ export function useFormik<Values extends FormikValues = FormikValues>({
}, [enableReinitialize, props.initialValues, resetForm]);

const validateField = useEventCallback(
(name: string) => {
(fieldName: string) => {
// This will efficiently validate a single field by avoiding state
// changes if the validation function is synchronous. It's different from
// what is called when using validateForm.

if (isFunction(fieldRegistry.current[name].validate)) {
const value = getIn(state.values, name);
const maybePromise = fieldRegistry.current[name].validate(value);
if (isFunction(fieldRegistry.current[fieldName].validate)) {
const value = getIn(state.values, fieldName);
const maybePromise = fieldRegistry.current[fieldName].validate(value);
if (isPromise(maybePromise)) {
// Only flip isValidating if the function is async.
dispatch({ type: 'SET_ISVALIDATING', payload: true });
Expand All @@ -412,15 +443,15 @@ export function useFormik<Values extends FormikValues = FormikValues>({
.then((error: string) => {
dispatch({
type: 'SET_FIELD_ERROR',
payload: { field: name, value: error },
payload: { field: fieldName, value: error },
});
dispatch({ type: 'SET_ISVALIDATING', payload: false });
});
} else {
dispatch({
type: 'SET_FIELD_ERROR',
payload: {
field: name,
field: fieldName,
value: maybePromise as string | undefined,
},
});
Expand Down Expand Up @@ -487,7 +518,7 @@ export function useFormik<Values extends FormikValues = FormikValues>({
},
});
return validateOnChange && shouldValidate
? validateFormWithLowPriority(setIn(state.values, field, value))
? validateFormWithLowPriority(setIn(state.values, field, value), field)
: Promise.resolve();
},
[validateFormWithLowPriority, state.values, validateOnChange]
Expand Down Expand Up @@ -572,7 +603,7 @@ export function useFormik<Values extends FormikValues = FormikValues>({
},
});
return validateOnBlur && shouldValidate
? validateFormWithLowPriority(state.values)
? validateFormWithLowPriority(state.values, field)
: Promise.resolve();
},
[validateFormWithLowPriority, state.values, validateOnBlur]
Expand Down
65 changes: 26 additions & 39 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3249,11 +3249,6 @@ case-sensitive-paths-webpack-plugin@^2.2.0:
resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.2.0.tgz#3371ef6365ef9c25fa4b81c16ace0e9c7dc58c3e"
integrity sha512-u5ElzokS8A1pm9vM3/iDgTcI3xqHxuCao94Oz8etI3cf0Tio0p8izkDYbTIn09uP3yUUr6+veaE6IkjnTYS46g==

case@^1.2.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/case/-/case-1.6.1.tgz#fa9ce79bb6f68f21650c419ae9c47b079308714f"
integrity sha512-N0rDB5ftMDKANGsIBRWPWcG0VIKtirgqcXb2vKFi66ySAjXVEwbfCN7ass1mkdXO8fbol3RfbWlQ9KyBX2F/Gg==

caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
Expand Down Expand Up @@ -5144,10 +5139,10 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"

fn-name@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-1.0.1.tgz#de8d8a15388b33cbf2145782171f73770c6030f0"
integrity sha1-3o2KFTiLM8vyFFeCFx9zdwxgMPA=
fn-name@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7"
integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=

focus-lock@^0.6.3:
version "0.6.3"
Expand Down Expand Up @@ -7178,7 +7173,7 @@ lodash.unescape@4.0.1:
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=

lodash@^4.11.2, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5, lodash@^4.3.0:
lodash@^4.11.2, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5, lodash@^4.3.0:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
Expand Down Expand Up @@ -8625,7 +8620,7 @@ prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2,
object-assign "^4.1.1"
react-is "^16.8.1"

property-expr@^1.2.0:
property-expr@^1.5.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f"
integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==
Expand Down Expand Up @@ -10392,6 +10387,11 @@ symbol.prototype.description@^1.0.0:
dependencies:
has-symbols "^1.0.0"

synchronous-promise@^2.0.6:
version "2.0.9"
resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.9.tgz#b83db98e9e7ae826bf9c8261fd8ac859126c780a"
integrity sha512-LO95GIW16x69LuND1nuuwM4pjgFGupg7pZ/4lU86AmchPKrhk0o2tpMU2unXRrqo81iAFe1YJ0nAGEVwsrZAgg==

table@^5.2.3:
version "5.3.3"
resolved "https://registry.yarnpkg.com/table/-/table-5.3.3.tgz#eae560c90437331b74200e011487a33442bd28b4"
Expand Down Expand Up @@ -10579,10 +10579,10 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==

toposort@^0.2.10:
version "0.2.12"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-0.2.12.tgz#c7d2984f3d48c217315cc32d770888b779491e81"
integrity sha1-x9KYTz1IwhcxXMMtdwiIt3lJHoE=
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=

tough-cookie@^2.3.3, tough-cookie@^2.3.4:
version "2.5.0"
Expand Down Expand Up @@ -10733,11 +10733,6 @@ type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"

type-name@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/type-name/-/type-name-2.0.2.tgz#efe7d4123d8ac52afff7f40c7e4dec5266008fb4"
integrity sha1-7+fUEj2KxSr/9/QMfk3sUmYAj7Q=

typed-styles@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9"
Expand Down Expand Up @@ -10903,13 +10898,6 @@ unist-util-visit@^1.1.0:
dependencies:
unist-util-visit-parents "^2.0.0"

universal-promise@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/universal-promise/-/universal-promise-1.1.0.tgz#563f9123372940839598c9d48be5ec33fa69cff1"
integrity sha1-Vj+RIzcpQIOVmMnUi+XsM/ppz/E=
dependencies:
promise "^7.1.1"

universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
Expand Down Expand Up @@ -11505,15 +11493,14 @@ yargs@^12.0.2:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^11.1.1"

yup@0.21.3:
version "0.21.3"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.21.3.tgz#46fc72b46cb58a1e70f4cb78cb645209632e193a"
integrity sha1-RvxytGy1ih5w9Mt4y2RSCWMuGTo=
dependencies:
case "^1.2.1"
fn-name "~1.0.1"
lodash "^4.17.0"
property-expr "^1.2.0"
toposort "^0.2.10"
type-name "^2.0.1"
universal-promise "^1.0.1"
yup@^0.27.0:
version "0.27.0"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.27.0.tgz#f8cb198c8e7dd2124beddc2457571329096b06e7"
integrity sha512-v1yFnE4+u9za42gG/b/081E7uNW9mUj3qtkmelLbW5YPROZzSH/KUUyJu9Wt8vxFJcT9otL/eZopS0YK1L5yPQ==
dependencies:
"@babel/runtime" "^7.0.0"
fn-name "~2.0.1"
lodash "^4.17.11"
property-expr "^1.5.0"
synchronous-promise "^2.0.6"
toposort "^2.0.2"

0 comments on commit 7ba657c

Please sign in to comment.