Skip to content

Commit 6983738

Browse files
committed
feat: new reset API
1 parent a11f1b7 commit 6983738

4 files changed

Lines changed: 108 additions & 44 deletions

File tree

packages/vee-validate/src/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ export type SubmitEvent = Event & { target: HTMLFormElement };
3030

3131
export type GenericValidateFunction = (value: any) => boolean | string | Promise<boolean | string>;
3232

33+
export interface FormState<TValues> {
34+
values: TValues;
35+
errors: Partial<Record<keyof TValues, string | undefined>>;
36+
dirty: Partial<Record<keyof TValues, boolean>>;
37+
touched: Partial<Record<keyof TValues, boolean>>;
38+
}
39+
3340
export interface FormContext<TValues extends Record<string, any> = Record<string, any>> {
3441
register(field: any): void;
3542
unregister(field: any): void;
@@ -45,7 +52,7 @@ export interface FormContext<TValues extends Record<string, any> = Record<string
4552
setTouched: (fields: Partial<Record<keyof TValues, boolean>>) => void;
4653
setFieldDirty: (field: keyof TValues, isDirty: boolean) => void;
4754
setDirty: (fields: Partial<Record<keyof TValues, boolean>>) => void;
48-
reset: () => void;
55+
reset: (state?: Partial<FormState<TValues>>) => void;
4956
}
5057

5158
type SubmissionContext<TValues extends Record<string, any> = Record<string, any>> = {

packages/vee-validate/src/useField.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
getCurrentInstance,
1313
unref,
1414
InjectionKey,
15+
WatchStopHandle,
1516
} from 'vue';
1617
import { validate as validateValue } from './validate';
1718
import { FormContext, ValidationResult, MaybeReactive, GenericValidateFunction, FieldMeta } from './types';
@@ -52,7 +53,7 @@ export function useField(name: MaybeReactive<string>, rules: RuleExpression, opt
5253
);
5354

5455
const form = injectWithSelf(FormSymbol);
55-
const { meta, errors, handleBlur, handleInput, reset, setValidationState, value, checked } = useValidationState({
56+
const { meta, errors, handleBlur, handleInput, resetField, setValidationState, value, checked } = useValidationState({
5657
name,
5758
// make sure to unref initial value because of possible refs passed in
5859
initValue: unref(initialValue),
@@ -113,6 +114,23 @@ export function useField(name: MaybeReactive<string>, rules: RuleExpression, opt
113114
meta.dirty = isDirty;
114115
}
115116

117+
let unwatchValue: WatchStopHandle;
118+
function watchValue() {
119+
if (validateOnValueUpdate) {
120+
unwatchValue = watch(value, validate, {
121+
deep: true,
122+
});
123+
}
124+
}
125+
126+
watchValue();
127+
128+
function reset() {
129+
unwatchValue?.();
130+
resetField();
131+
watchValue();
132+
}
133+
116134
const field = {
117135
fid,
118136
name,
@@ -134,12 +152,6 @@ export function useField(name: MaybeReactive<string>, rules: RuleExpression, opt
134152
setDirty,
135153
};
136154

137-
if (validateOnValueUpdate) {
138-
watch(value, validate, {
139-
deep: true,
140-
});
141-
}
142-
143155
if (isRef(rules) && typeof unref(rules) !== 'function') {
144156
watch(rules, validate, {
145157
deep: true,
@@ -285,17 +297,17 @@ function useValidationState({
285297
}
286298

287299
// Resets the validation state
288-
const reset = () => {
300+
function resetField() {
289301
value.value = getFromPath(unref(formInitialValues), unref(name)) ?? initValue;
290302
errors.value = [];
291303
resetFlags();
292-
};
304+
}
293305

294306
return {
295307
meta,
296308
errors,
297309
setValidationState,
298-
reset,
310+
resetField,
299311
handleBlur,
300312
handleInput,
301313
value,

packages/vee-validate/src/useForm.ts

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SubmitEvent,
1010
ValidationResult,
1111
MaybeReactive,
12+
FormState,
1213
} from './types';
1314
import { getFromPath, isYupValidator, keysOf, setInPath, unsetPath } from './utils';
1415
import { FormErrorsSymbol, FormInitialValues, FormSymbol } from './symbols';
@@ -85,7 +86,11 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
8586
});
8687

8788
// initial form values
88-
const { initialValues } = useFormInitialValues<TValues>(fieldsById, formValues, opts?.initialValues);
89+
const { initialValues, setInitialValues } = useFormInitialValues<TValues>(
90+
fieldsById,
91+
formValues,
92+
opts?.initialValues
93+
);
8994

9095
// form meta aggregations
9196
const meta = useFormMeta(fields, initialValues);
@@ -213,8 +218,25 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
213218
/**
214219
* Resets all fields
215220
*/
216-
const handleReset = () => {
221+
const reset = (state?: Partial<FormState<TValues>>) => {
222+
// set initial values if provided
223+
if (state?.values) {
224+
setInitialValues(state.values);
225+
}
226+
227+
// Reset all fields state
217228
fields.value.forEach((f: any) => f.reset());
229+
230+
// set explicit state afterwards
231+
if (state?.dirty) {
232+
setDirty(state.dirty);
233+
}
234+
if (state?.touched) {
235+
setTouched(state.touched);
236+
}
237+
if (state?.errors) {
238+
setErrors(state.errors);
239+
}
218240
};
219241

220242
function registerField(field: FieldComposite) {
@@ -294,7 +316,7 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
294316
setTouched,
295317
setFieldDirty,
296318
setDirty,
297-
reset: handleReset,
319+
reset,
298320
};
299321

300322
const validate = async () => {
@@ -375,7 +397,6 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
375397
});
376398

377399
// Provide injections
378-
provide(FormInitialValues, initialValues);
379400
provide(FormSymbol, formCtx as FormContext);
380401
provide(FormErrorsSymbol, errors);
381402

@@ -385,7 +406,8 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
385406
values: formValues,
386407
validate,
387408
isSubmitting,
388-
handleReset,
409+
handleReset: () => reset(),
410+
resetForm: reset,
389411
handleSubmit,
390412
submitForm,
391413
setFieldError,
@@ -482,43 +504,59 @@ async function validateYupSchema<TValues>(
482504
/**
483505
* Manages the initial values prop
484506
*/
485-
function useFormInitialValues<TValues>(
507+
function useFormInitialValues<TValues extends Record<string, any>>(
486508
fields: Ref<Record<keyof TValues, any>>,
487509
formValues: TValues,
488510
providedValues?: MaybeReactive<TValues>
489511
) {
490-
const initialValues = computed<TValues>(() => {
491-
if (isRef(providedValues)) {
492-
return providedValues.value;
493-
}
494-
495-
return providedValues || ({} as TValues);
512+
const initialValues = ref<TValues>((unref(providedValues) as TValues) || ({} as TValues));
513+
// acts as a read only proxy of the initial values object
514+
const computedInitials = computed<TValues>(() => {
515+
return initialValues.value as TValues;
496516
});
497517

498-
// Watch initial values for changes, and update the pristine (non-dirty and non-touched fields)
499-
// we exclude dirty and untouched fields because it's unlikely you want to change the form values using initial values
500-
// we mostly watch them for API population or newly inserted fields
501-
watch(
502-
initialValues,
503-
value => {
504-
const isSafeToUpdate = (f: any) => f.meta.dirty || f.meta.touched;
505-
keysOf(fields.value).forEach(fieldPath => {
506-
const field = fields.value[fieldPath];
507-
const isFieldDirty = Array.isArray(field) ? field.some(isSafeToUpdate) : isSafeToUpdate(field);
508-
if (isFieldDirty) {
509-
return;
510-
}
518+
function setInitialValues(values: Partial<TValues>, updateFields = false) {
519+
initialValues.value = {
520+
...initialValues.value,
521+
...values,
522+
};
511523

512-
const newValue = getFromPath(value, fieldPath as string);
513-
setInPath(formValues, fieldPath as string, newValue);
514-
});
515-
},
516-
{
517-
deep: true,
524+
if (!updateFields) {
525+
return;
518526
}
519-
);
527+
528+
// update the pristine (non-dirty and non-touched fields)
529+
// we exclude dirty and untouched fields because it's unlikely you want to change the form values using initial values
530+
// we mostly watch them for API population or newly inserted fields
531+
const isSafeToUpdate = (f: any) => f.meta.dirty || f.meta.touched;
532+
keysOf(fields.value).forEach(fieldPath => {
533+
const field = fields.value[fieldPath];
534+
const isFieldDirty = Array.isArray(field) ? field.some(isSafeToUpdate) : isSafeToUpdate(field);
535+
if (isFieldDirty) {
536+
return;
537+
}
538+
539+
const newValue = getFromPath(initialValues.value, fieldPath as string);
540+
setInPath(formValues, fieldPath as string, newValue);
541+
});
542+
}
543+
544+
if (isRef(providedValues)) {
545+
watch(
546+
providedValues,
547+
value => {
548+
setInitialValues(value, true);
549+
},
550+
{
551+
deep: true,
552+
}
553+
);
554+
}
555+
556+
provide(FormInitialValues, computedInitials);
520557

521558
return {
522-
initialValues,
559+
initialValues: computedInitials,
560+
setInitialValues,
523561
};
524562
}

packages/vee-validate/tests/Form.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,23 @@ describe('<Form />', () => {
111111
});
112112

113113
const error = wrapper.$el.querySelector('#error');
114+
const input = wrapper.$el.querySelector('input');
115+
114116
expect(error.textContent).toBe('');
115117

116118
wrapper.$el.querySelector('#submit').click();
117119
await flushPromises();
118120

119121
expect(error.textContent).toBe(REQUIRED_MESSAGE);
120122

123+
setValue(input, 'value');
124+
await flushPromises();
121125
wrapper.$el.querySelector('#reset').click();
122126
await flushPromises();
123127

128+
// value was reset
129+
expect(input.value).toBe('');
130+
// errors were cleared
124131
expect(error.textContent).toBe('');
125132
expect(isReset).toBe(true);
126133
});

0 commit comments

Comments
 (0)