Skip to content

Commit

Permalink
feat(useFormState): add revalidateOnChange dependencies array option,…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
mturley authored Feb 14, 2022
1 parent a52a0b7 commit a4e3672
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 5 deletions.
5 changes: 4 additions & 1 deletion src/hooks/useFormState/useFormState.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ function useFormState<TFieldValues>(
// an object with values as useFormField calls
[key in keyof TFieldValues]: IFormField<TFieldValues[key]>;
},
yupOptions?: yup.ValidateOptions
options: {
revalidateOnChange?: unknown[];
yupOptions?: ValidateOptions;
} = {}
): IFormState<TFieldValues>;

interface IFormField<T> {
Expand Down
27 changes: 23 additions & 4 deletions src/hooks/useFormState/useFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,13 @@ export const useFormField = <T>(

export const useFormState = <TFieldValues>(
fields: FormFields<TFieldValues>,
yupOptions: ValidateOptions = {}
options: {
revalidateOnChange?: unknown[];
yupOptions?: ValidateOptions;
} = {}
): IFormState<TFieldValues> => {
const { revalidateOnChange = [], yupOptions = {} } = options;

const fieldKeys = Object.keys(fields) as (keyof TFieldValues)[];
const values: TFieldValues = fieldKeys.reduce(
(newObj, key) => ({ ...newObj, [key]: fields[key].value }),
Expand All @@ -144,13 +149,19 @@ export const useFormState = <TFieldValues>(
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<yup.ValidationError | null>(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<TFieldValues[key]> }
Expand All @@ -170,7 +181,15 @@ export const useFormState = <TFieldValues>(
}
});
}
}, [fieldKeys, fields, hasRunInitialValidation, validationError, values, yupOptions]);
}, [
fieldKeys,
fields,
hasRunInitialValidation,
validationError,
values,
revalidateOnChange,
yupOptions,
]);

type ErrorsByField = { [key in keyof TFieldValues]: yup.ValidationError };
const errorsByField =
Expand Down

0 comments on commit a4e3672

Please sign in to comment.