diff --git a/packages/vee-validate/src/useForm.ts b/packages/vee-validate/src/useForm.ts index 030333aa4..8f49a3abd 100644 --- a/packages/vee-validate/src/useForm.ts +++ b/packages/vee-validate/src/useForm.ts @@ -626,12 +626,17 @@ export function useForm = Record isEqual(i, unref(field.checkedValue))); - if (valueIdx > -1) { - const newVal = [...currentGroupValue]; - newVal.splice(valueIdx, 1); - setFieldValue(fieldName, newVal as any, { force: true }); + if (isSameGroup && !shouldKeepValue) { + if (Array.isArray(currentGroupValue)) { + const valueIdx = currentGroupValue.findIndex(i => isEqual(i, unref(field.checkedValue))); + if (valueIdx > -1) { + const newVal = [...currentGroupValue]; + newVal.splice(valueIdx, 1); + setFieldValue(fieldName, newVal as any, { force: true }); + } + } else if (currentGroupValue === unref(field.checkedValue)) { + // Remove field if it is a group but does not have an array value, like for radio inputs #3963 + unsetPath(formValues, fieldName); } } @@ -647,7 +652,8 @@ export function useForm = Record', () => { Coffee Tea + Coke {{ errors }} @@ -2760,3 +2761,163 @@ describe('
', () => { expect(value.value).toBe(true); }); }); + +// #3963 +test('unmounted radio fields gets unregistered and their submitted values are kept if configured on the form level', async () => { + let showFields!: Ref; + const spy = jest.fn(); + const wrapper = mountWithHoc({ + setup() { + showFields = ref(true); + + return { + showFields, + onSubmit(values: any) { + spy(values); + }, + }; + }, + template: ` + + + + Coke + + {{ errors }} + + + + `, + }); + + await flushPromises(); + const errors = wrapper.$el.querySelector('#errors'); + const button = wrapper.$el.querySelector('button'); + const inputs = wrapper.$el.querySelectorAll('input'); + + wrapper.$el.querySelector('button').click(); + await flushPromises(); + expect(errors.textContent).toBeTruthy(); + setChecked(inputs[1]); + + await flushPromises(); + button.click(); + await flushPromises(); + const expected = { + drink: 'Tea', + }; + expect(spy).toHaveBeenLastCalledWith(expected); + + showFields.value = false; + await flushPromises(); + expect(errors.textContent).toBe('{}'); + button.click(); + await flushPromises(); + expect(spy).toHaveBeenLastCalledWith(expected); +}); + +// #3963 +test('unmounted radio fields gets unregistered and their submitted values are removed', async () => { + let showFields!: Ref; + const spy = jest.fn(); + const wrapper = mountWithHoc({ + setup() { + showFields = ref(true); + + return { + showFields, + onSubmit(values: any) { + spy(values); + }, + }; + }, + template: ` + + + + Coke + + {{ errors }} + + + + `, + }); + + await flushPromises(); + const errors = wrapper.$el.querySelector('#errors'); + const button = wrapper.$el.querySelector('button'); + const inputs = wrapper.$el.querySelectorAll('input'); + + wrapper.$el.querySelector('button').click(); + await flushPromises(); + expect(errors.textContent).toBeTruthy(); + setChecked(inputs[1]); + + await flushPromises(); + button.click(); + await flushPromises(); + expect(spy).toHaveBeenLastCalledWith({ drink: 'Tea' }); + + showFields.value = false; + await flushPromises(); + expect(errors.textContent).toBe('{}'); + button.click(); + await flushPromises(); + expect(spy).toHaveBeenLastCalledWith({}); +}); + +// #3963 +test('unmounted radio fields gets unregistered and their values are removed if configured on the field level', async () => { + const showFields = ref(true); + + const wrapper = mountWithHoc({ + setup() { + return { + showFields, + }; + }, + template: ` + + + + Coke + + {{ errors }} + {{ values }} + + + + `, + }); + + await flushPromises(); + const errors = wrapper.$el.querySelector('#errors'); + const values = wrapper.$el.querySelector('#values'); + const inputs = wrapper.$el.querySelectorAll('input'); + + wrapper.$el.querySelector('button').click(); + await flushPromises(); + expect(errors.textContent).toBeTruthy(); + setChecked(inputs[1]); + + await flushPromises(); + expect(JSON.parse(values.textContent)).toEqual({ + drink: 'Tea', + }); + + showFields.value = false; + await flushPromises(); + // errors were cleared + expect(errors.textContent).toBe('{}'); + expect(JSON.parse(values.textContent)).toEqual({}); +});