Skip to content

Commit

Permalink
feat(useformstate): add markSaved and other methods for manipulatin…
Browse files Browse the repository at this point in the history
…g multiple form fields at once (#84)

See updated comments on the prop interfaces. `markSaved` is useful to set all fields as clean
after saving form values to storage, so that revert() will restore those values.
Other new methods apply the existing field-level methods to multiple fields at once.
  • Loading branch information
mturley authored Feb 1, 2022
1 parent 008bfa0 commit 2beb616
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 17 deletions.
21 changes: 13 additions & 8 deletions src/hooks/useFormState/useFormState.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ interface IFormField<T> {
setValue: React.Dispatch<React.SetStateAction<T>>;
defaultValue: T;
cleanValue: T;
reinitialize: (value: T) => void; // Sets defaultValue, cleanValue and value
prefill: (value: T) => void; // Sets cleanValue and value
clear: () => void; // Sets value to defaultValue
revert: () => void; // Sets value to cleanValue
isDirty: boolean; // True if value is different from cleanValue
reinitialize: (value: T) => void; // Sets defaultValue, cleanValue and current value. Value will be restored on revert or clear. Useful if initial values are loaded asynchronously.
prefill: (value: T) => void; // Sets cleanValue and current value. Value will be restored on revert but not clear. Useful if values to be edited are loaded asynchronously but retains separate defaultValue for clear.
markSaved: () => void; // Sets cleanValue to current value. Current value will be restored on revert but not clear. Useful if values have been saved to storage but the form is still open.
clear: () => void; // Sets current value to defaultValue.
revert: () => void; // Sets current value to cleanValue.
isDirty: boolean; // True if current value is different from cleanValue.
isTouched: boolean;
setIsTouched: (isTouched: boolean) => void;
schema: yup.AnySchema<T>;
Expand All @@ -72,10 +73,14 @@ interface IFormState<TFieldValues> {
fields: ValidatedFormFields<TFieldValues>;
values: TFieldValues; // For convenience in submitting forms (values are also included in fields property)
isDirty: boolean;
isValid: boolean;
isTouched: boolean;
clear: () => void;
revert: () => void;
isValid: boolean;
setValues: (newValues: Partial<TFieldValues>) => void; // Sets multiple values at once.
reinitialize: (newValues: Partial<TFieldValues>) => void; // Reinitializes multiple values at once (see IFormField<T>)
prefill: (newValues: Partial<TFieldValues>) => void; // Prefills multiple values at once (see IFormField<T>)
markSaved: () => void; // Marks all fields as clean/saved (see IFormField<T>)
clear: () => void; // Clears all fields (see IFormField<T>)
revert: () => void; // Reverts all fields (see IFormField<T>)
}
```

Expand Down
41 changes: 32 additions & 9 deletions src/hooks/useFormState/useFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ export interface IFormField<T> {
setValue: React.Dispatch<React.SetStateAction<T>>;
defaultValue: T;
cleanValue: T;
reinitialize: (value: T) => void; // Sets defaultValue, cleanValue and value
prefill: (value: T) => void; // Sets cleanValue and value
clear: () => void; // Sets value to defaultValue
revert: () => void; // Sets value to cleanValue
isDirty: boolean; // True if value is different from cleanValue
reinitialize: (value: T) => void; // Sets defaultValue, cleanValue and current value. Value will be restored on revert or clear. Useful if initial values are loaded asynchronously.
prefill: (value: T) => void; // Sets cleanValue and current value. Value will be restored on revert but not clear. Useful if values to be edited are loaded asynchronously but retains separate defaultValue for clear.
markSaved: () => void; // Sets cleanValue to current value. Current value will be restored on revert but not clear. Useful if values have been saved to storage but the form is still open.
clear: () => void; // Sets current value to defaultValue.
revert: () => void; // Sets current value to cleanValue.
isDirty: boolean; // True if current value is different from cleanValue.
isTouched: boolean;
setIsTouched: (isTouched: boolean) => void;
schema: yup.AnySchema<T>;
Expand All @@ -40,10 +41,14 @@ export interface IFormState<TFieldValues> {
fields: ValidatedFormFields<TFieldValues>;
values: TFieldValues; // For convenience in submitting forms (values are also included in fields property)
isDirty: boolean;
isValid: boolean;
isTouched: boolean;
clear: () => void;
revert: () => void;
isValid: boolean;
setValues: (newValues: Partial<TFieldValues>) => void; // Sets multiple values at once.
reinitialize: (newValues: Partial<TFieldValues>) => void; // Reinitializes multiple values at once (see IFormField<T>)
prefill: (newValues: Partial<TFieldValues>) => void; // Prefills multiple values at once (see IFormField<T>)
markSaved: () => void; // Marks all fields as clean/saved (see IFormField<T>)
clear: () => void; // Clears all fields (see IFormField<T>)
revert: () => void; // Reverts all fields (see IFormField<T>)
}

// The generic T type variable is the type of the field's value (the T in IFormField<T>).
Expand Down Expand Up @@ -79,6 +84,9 @@ export const useFormField = <T>(
setCleanValue(value);
setValue(value);
},
markSaved: () => {
setCleanValue(value);
},
clear: () => {
setValue(defaultValue);
setIsTouched(options.initialTouched || false);
Expand Down Expand Up @@ -153,10 +161,12 @@ export const useFormState = <TFieldValues>(
{} as ErrorsByField
) || ({} as ErrorsByField);

type TFieldValue = TFieldValues[keyof TFieldValues];

const validatedFields: ValidatedFormFields<TFieldValues> = fieldKeys.reduce((newObj, key) => {
const field = fields[key];
const error = errorsByField ? errorsByField[key] : null;
const validatedField: IValidatedFormField<TFieldValues[keyof TFieldValues]> = {
const validatedField: IValidatedFormField<TFieldValue> = {
...field,
error,
isValid: !error,
Expand All @@ -171,6 +181,19 @@ export const useFormState = <TFieldValues>(
isDirty,
isTouched,
isValid: hasRunInitialValidation && !validationError,
setValues: (newValues: Partial<TFieldValues>) =>
(Object.keys(newValues) as (keyof TFieldValues)[]).forEach(
(key) => fields[key] && fields[key].setValue(newValues[key] as TFieldValue)
),
reinitialize: (newValues: Partial<TFieldValues>) =>
(Object.keys(newValues) as (keyof TFieldValues)[]).forEach(
(key) => fields[key] && fields[key].reinitialize(newValues[key] as TFieldValue)
),
prefill: (newValues: Partial<TFieldValues>) =>
(Object.keys(newValues) as (keyof TFieldValues)[]).forEach(
(key) => fields[key] && fields[key].prefill(newValues[key] as TFieldValue)
),
markSaved: () => fieldKeys.forEach((key) => fields[key].markSaved()),
clear: () => fieldKeys.forEach((key) => fields[key].clear()),
revert: () => fieldKeys.forEach((key) => fields[key].revert()),
};
Expand Down

0 comments on commit 2beb616

Please sign in to comment.