From a4e367267c7b953c1fe5ce925aab1270d5ab8e63 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Mon, 14 Feb 2022 11:56:41 -0500 Subject: [PATCH] feat(useFormState): add revalidateOnChange dependencies array option, refactor options into object (#94) Replaces the yupOptions argument with an options object including yupOptions and a new option called revalidateOnChange, which is an array of arbitrary variables. If any of these variables change since the last validation, the form will revalidate even if none of the values changed. This can be used to force revalidation when some asynchronous task is done, like checking a connection from credentials stored in form state. BREAKING CHANGE: Any forms using the yupOptions argument will need to wrap it in an object with the property yupOptions instead of passing it directly as a top-level argument. --- .../useFormState/useFormState.stories.mdx | 5 +++- src/hooks/useFormState/useFormState.ts | 27 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/hooks/useFormState/useFormState.stories.mdx b/src/hooks/useFormState/useFormState.stories.mdx index c30742a..6271e16 100644 --- a/src/hooks/useFormState/useFormState.stories.mdx +++ b/src/hooks/useFormState/useFormState.stories.mdx @@ -47,7 +47,10 @@ function useFormState( // an object with values as useFormField calls [key in keyof TFieldValues]: IFormField; }, - yupOptions?: yup.ValidateOptions + options: { + revalidateOnChange?: unknown[]; + yupOptions?: ValidateOptions; + } = {} ): IFormState; interface IFormField { diff --git a/src/hooks/useFormState/useFormState.ts b/src/hooks/useFormState/useFormState.ts index 627020a..e595a9e 100644 --- a/src/hooks/useFormState/useFormState.ts +++ b/src/hooks/useFormState/useFormState.ts @@ -134,8 +134,13 @@ export const useFormField = ( export const useFormState = ( fields: FormFields, - yupOptions: ValidateOptions = {} + options: { + revalidateOnChange?: unknown[]; + yupOptions?: ValidateOptions; + } = {} ): IFormState => { + const { revalidateOnChange = [], yupOptions = {} } = options; + const fieldKeys = Object.keys(fields) as (keyof TFieldValues)[]; const values: TFieldValues = fieldKeys.reduce( (newObj, key) => ({ ...newObj, [key]: fields[key].value }), @@ -144,13 +149,19 @@ export const useFormState = ( const isDirty = fieldKeys.some((key) => fields[key].isDirty); const isTouched = fieldKeys.some((key) => fields[key].isTouched); - // Memoize the validation, only recompute if the field values changed + // Memoize the validation, only recompute if the field values or revalidateOnChange dependencies changed const [validationError, setValidationError] = React.useState(null); const [hasRunInitialValidation, setHasRunInitialValidation] = React.useState(false); const lastValuesRef = React.useRef(values); + const lastRevalidateDepsRef = React.useRef(revalidateOnChange); React.useEffect(() => { - if (!hasRunInitialValidation || !equal(lastValuesRef.current, values)) { + if ( + !hasRunInitialValidation || + !equal(lastRevalidateDepsRef.current, revalidateOnChange) || + !equal(lastValuesRef.current, values) + ) { lastValuesRef.current = values; + lastRevalidateDepsRef.current = revalidateOnChange; const schemaShape = fieldKeys.reduce( (newObj, key) => ({ ...newObj, [key]: fields[key].schema }), {} as { [key in keyof TFieldValues]: yup.AnySchema } @@ -170,7 +181,15 @@ export const useFormState = ( } }); } - }, [fieldKeys, fields, hasRunInitialValidation, validationError, values, yupOptions]); + }, [ + fieldKeys, + fields, + hasRunInitialValidation, + validationError, + values, + revalidateOnChange, + yupOptions, + ]); type ErrorsByField = { [key in keyof TFieldValues]: yup.ValidationError }; const errorsByField =