Skip to content

Commit e5c42fc

Browse files
authored
Fix: use deep quality check for checkbox values #3883 (#3885)
1 parent 4dbafc1 commit e5c42fc

File tree

3 files changed

+59
-4
lines changed

3 files changed

+59
-4
lines changed

packages/vee-validate/src/useField.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,9 @@ function useCheckboxField<TValue = unknown>(
436436
const currentValue = unref(field.value);
437437
const checkedVal = unref(checkedValue);
438438

439-
return Array.isArray(currentValue) ? currentValue.includes(checkedVal) : checkedVal === currentValue;
439+
return Array.isArray(currentValue)
440+
? currentValue.findIndex(v => isEqual(v, checkedVal)) >= 0
441+
: isEqual(checkedVal, currentValue);
440442
});
441443

442444
function handleCheckboxChange(e: unknown, shouldValidate = true) {

packages/vee-validate/src/utils/common.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { isIndex, isNullOrUndefined, isObject, toNumber } from '../../../shared';
21
import { getCurrentInstance, inject, InjectionKey, warn as vueWarning } from 'vue';
2+
import isEqual from 'fast-deep-equal';
3+
import { isIndex, isNullOrUndefined, isObject, toNumber } from '../../../shared';
34
import { isContainerValue, isEmptyContainer, isNotNestedPath } from './assertions';
45
import { PrivateFieldContext } from '../types';
56

@@ -169,13 +170,14 @@ export function resolveNextCheckboxValue<T>(currentValue: T[], checkedValue: T,
169170
export function resolveNextCheckboxValue<T>(currentValue: T | T[], checkedValue: T, uncheckedValue: T) {
170171
if (Array.isArray(currentValue)) {
171172
const newVal = [...currentValue];
172-
const idx = newVal.indexOf(checkedValue);
173+
// Use isEqual since checked object values can possibly fail the equality check #3883
174+
const idx = newVal.findIndex(v => isEqual(v, checkedValue));
173175
idx >= 0 ? newVal.splice(idx, 1) : newVal.push(checkedValue);
174176

175177
return newVal;
176178
}
177179

178-
return currentValue === checkedValue ? uncheckedValue : checkedValue;
180+
return isEqual(currentValue, checkedValue) ? uncheckedValue : checkedValue;
179181
}
180182

181183
// https://github.com/bameyrick/throttle-typescript

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,57 @@ describe('<Form />', () => {
12071207
expect(values.textContent).toBe(['Coke', ''].toString());
12081208
});
12091209

1210+
test('checkboxes with object values', async () => {
1211+
const wrapper = mountWithHoc({
1212+
setup() {
1213+
const schema = yup.object({
1214+
drink: yup.array().required().min(1),
1215+
});
1216+
1217+
return {
1218+
schema,
1219+
};
1220+
},
1221+
template: `
1222+
<VForm :validation-schema="schema" v-slot="{ errors, values }">
1223+
<Field name="drink" as="input" type="checkbox" :value="{ id: '' }" /> Coffee
1224+
<Field name="drink" as="input" type="checkbox" :value="{ id: 'tea' }" /> Tea
1225+
<Field name="drink" as="input" type="checkbox" :value="{ id: 'coke' }" /> Coke
1226+
1227+
<span id="err">{{ errors.drink }}</span>
1228+
<span id="values">{{ JSON.stringify(values.drink) }}</span>
1229+
1230+
<button>Submit</button>
1231+
</VForm>
1232+
`,
1233+
});
1234+
1235+
const err = wrapper.$el.querySelector('#err');
1236+
const values = wrapper.$el.querySelector('#values');
1237+
const inputs = wrapper.$el.querySelectorAll('input');
1238+
1239+
wrapper.$el.querySelector('button').click();
1240+
await flushPromises();
1241+
expect(err.textContent).toBe('drink is a required field');
1242+
setChecked(inputs[2]);
1243+
await flushPromises();
1244+
expect(err.textContent).toBe('');
1245+
1246+
setChecked(inputs[0]);
1247+
await flushPromises();
1248+
expect(err.textContent).toBe('');
1249+
1250+
setChecked(inputs[1]);
1251+
await flushPromises();
1252+
expect(err.textContent).toBe('');
1253+
1254+
expect(values.textContent).toBe(JSON.stringify([{ id: 'coke' }, { id: '' }, { id: 'tea' }]));
1255+
1256+
setChecked(inputs[1], false);
1257+
await flushPromises();
1258+
expect(values.textContent).toBe(JSON.stringify([{ id: 'coke' }, { id: '' }]));
1259+
});
1260+
12101261
test('checkboxes v-model value syncing', async () => {
12111262
const drinks = ref<string[]>([]);
12121263
const wrapper = mountWithHoc({

0 commit comments

Comments
 (0)