Skip to content

Commit cad12ba

Browse files
committed
fix: prevent toggle checkboxes when form resets closes #3551
1 parent 4b800e3 commit cad12ba

2 files changed

Lines changed: 63 additions & 2 deletions

File tree

packages/vee-validate/src/useForm.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
5353
): FormContext<TValues> {
5454
const formId = FORM_COUNTER++;
5555

56+
// Prevents fields from double resetting their values, which causes checkboxes to toggle their initial value
57+
// TODO: This won't be needed if we centralize all the state inside the `form` for form inputs
58+
let RESET_LOCK = false;
59+
5660
// A lookup containing fields or field groups
5761
const fieldsByPath: Ref<FieldPathLookup<TValues>> = ref({} as any);
5862

@@ -210,8 +214,8 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
210214
return;
211215
}
212216

213-
// Multiple checkboxes, and only one of them got updated
214217
if (isFieldGroup(fieldInstance) && fieldInstance[0]?.type === 'checkbox' && !Array.isArray(value)) {
218+
// Multiple checkboxes, and only one of them got updated
215219
const newValue = deepCopy(
216220
resolveNextCheckboxValue(getFromPath(formValues, field as string) || [], value, undefined)
217221
);
@@ -222,7 +226,7 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
222226

223227
let newValue = value;
224228
// Single Checkbox: toggles the field value unless the field is being reset then force it
225-
if (!isFieldGroup(fieldInstance) && fieldInstance.type === 'checkbox' && !force) {
229+
if (!isFieldGroup(fieldInstance) && fieldInstance.type === 'checkbox' && !force && !RESET_LOCK) {
226230
newValue = deepCopy(
227231
resolveNextCheckboxValue<TValues[T]>(
228232
getFromPath<TValues[T]>(formValues, field as string) as TValues[T],
@@ -277,6 +281,7 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
277281
* Resets all fields
278282
*/
279283
function resetForm(state?: Partial<FormState<TValues>>) {
284+
RESET_LOCK = true;
280285
// set initial values if provided
281286
if (state?.values) {
282287
setInitialValues(state.values);
@@ -293,6 +298,7 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
293298
return;
294299
}
295300

301+
// avoid resetting the field values, because they should've been reset already.
296302
applyFieldMutation(field, f => f.resetField());
297303
});
298304

@@ -302,6 +308,9 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
302308

303309
setErrors(state?.errors || {});
304310
submitCount.value = state?.submitCount || 0;
311+
nextTick(() => {
312+
RESET_LOCK = false;
313+
});
305314
}
306315

307316
function insertFieldAtPath(field: PrivateFieldContext, path: string) {

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2231,4 +2231,56 @@ describe('<Form />', () => {
22312231
} as InvalidSubmissionContext);
22322232
expect(validSpy).not.toHaveBeenCalled();
22332233
});
2234+
2235+
// #3551
2236+
test('resets checkboxes according to initial values', async () => {
2237+
const wrapper = mountWithHoc({
2238+
setup() {
2239+
return {
2240+
values: {
2241+
terms: true,
2242+
termsUnslotted: true,
2243+
array: ['coffee', 'tea'],
2244+
},
2245+
};
2246+
},
2247+
template: `
2248+
<VForm as="form" v-slot="{ resetForm }" :initial-values="values">
2249+
<Field v-slot="{ field }" name="terms" type="checkbox" :value="true" :unchecked-value="false">
2250+
<label>
2251+
<input type="checkbox" name="terms" v-bind="field" :value="true" :unchecked-value="false" />
2252+
</label>
2253+
</Field>
2254+
2255+
<Field name="termsUnslotted" type="checkbox" :value="true" :unchecked-value="false"></Field>
2256+
2257+
<Field name="array" type="checkbox" value="coffee"></Field>
2258+
<Field name="array" type="checkbox" value="tea"></Field>
2259+
2260+
<button id="reset1" type="button" @click="resetForm()">Reset</button>
2261+
<button id="reset2" type="button" @click="resetForm({ values: { terms: false, termsUnslotted: true, array: ['coffee'] } })">Reset</button>
2262+
</VForm>
2263+
`,
2264+
});
2265+
2266+
const inputAt = (idx: number) => wrapper.$el.querySelectorAll('input')[idx] as HTMLInputElement;
2267+
expect(inputAt(0).checked).toBe(true);
2268+
expect(inputAt(1).checked).toBe(true);
2269+
expect(inputAt(2).checked).toBe(true);
2270+
expect(inputAt(3).checked).toBe(true);
2271+
2272+
dispatchEvent('#reset1', 'click');
2273+
await flushPromises();
2274+
expect(inputAt(0).checked).toBe(true);
2275+
expect(inputAt(1).checked).toBe(true);
2276+
expect(inputAt(2).checked).toBe(true);
2277+
expect(inputAt(3).checked).toBe(true);
2278+
2279+
dispatchEvent('#reset2', 'click');
2280+
await flushPromises();
2281+
expect(inputAt(0).checked).toBe(false);
2282+
expect(inputAt(1).checked).toBe(true);
2283+
expect(inputAt(2).checked).toBe(true);
2284+
expect(inputAt(3).checked).toBe(false);
2285+
});
22342286
});

0 commit comments

Comments
 (0)