Skip to content

Commit 92efca1

Browse files
logaretmclaude
andcommitted
fix(rules): add required_if rule for conditional field validation (#4868)
The required_if rule was listed in the README and i18n locale files but was missing from the rules package since a prior refactor removed it. This re-implements the rule using the current simple validator pattern, enabling cross-field conditional required validation that works correctly on every submission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7d8cc52 commit 92efca1

4 files changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@vee-validate/rules": patch
3+
---
4+
5+
Fix required_if rule not re-evaluating on subsequent submissions (#4868)

packages/rules/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import numeric from './numeric';
2323
import one_of from './one_of';
2424
import regex from './regex';
2525
import required from './required';
26+
import required_if from './required_if';
2627
import size from './size';
2728
import url from './url';
2829
import { toTypedSchema } from './toTypedSchema';
@@ -54,6 +55,7 @@ export const all: Record<string, SimpleValidationRuleFunction<any, any>> = {
5455
one_of,
5556
regex,
5657
required,
58+
required_if,
5759
size,
5860
url,
5961
};
@@ -84,6 +86,7 @@ export {
8486
one_of,
8587
regex,
8688
required,
89+
required_if,
8790
size,
8891
url,
8992
toTypedSchema,

packages/rules/src/required_if.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { isEmpty } from './utils';
2+
import { isEmptyArray, isNullOrUndefined } from '../../shared';
3+
4+
const requiredIfValidator = (
5+
value: unknown,
6+
params: [unknown, ...unknown[]] | { target: unknown; values?: unknown[] },
7+
) => {
8+
let target: unknown;
9+
let values: unknown[] | undefined;
10+
11+
if (Array.isArray(params)) {
12+
target = params[0];
13+
values = params.slice(1);
14+
} else {
15+
target = params.target;
16+
values = params.values;
17+
}
18+
19+
// Determine if the field should be required
20+
let isRequired: boolean;
21+
22+
if (values && values.length) {
23+
// eslint-disable-next-line eqeqeq
24+
isRequired = values.some(val => val == target);
25+
} else {
26+
isRequired = !isNullOrUndefined(target) && !isEmptyArray(target) && !!String(target).trim().length;
27+
}
28+
29+
if (!isRequired) {
30+
return true;
31+
}
32+
33+
return !isEmpty(value);
34+
};
35+
36+
export default requiredIfValidator;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import validate from '../src/required_if';
2+
3+
test('validates required_if with array params', () => {
4+
// target has a matching value, field is empty -> invalid
5+
expect(validate('', ['Online Banking', 'Online Banking'])).toBe(false);
6+
expect(validate(null, ['Online Banking', 'Online Banking'])).toBe(false);
7+
expect(validate(undefined, ['Online Banking', 'Online Banking'])).toBe(false);
8+
expect(validate([], ['Online Banking', 'Online Banking'])).toBe(false);
9+
10+
// target has a matching value, field is filled -> valid
11+
expect(validate('Nabil', ['Online Banking', 'Online Banking'])).toBe(true);
12+
13+
// target does not match any value, field is empty -> valid (not required)
14+
expect(validate('', ['Cash', 'Online Banking'])).toBe(true);
15+
expect(validate(null, ['Cash', 'Online Banking'])).toBe(true);
16+
expect(validate(undefined, ['Cash', 'Online Banking'])).toBe(true);
17+
});
18+
19+
test('validates required_if with object params', () => {
20+
// target matches, field is empty -> invalid
21+
expect(validate('', { target: 'Online Banking', values: ['Online Banking'] })).toBe(false);
22+
23+
// target matches, field is filled -> valid
24+
expect(validate('Nabil', { target: 'Online Banking', values: ['Online Banking'] })).toBe(true);
25+
26+
// target does not match, field is empty -> valid
27+
expect(validate('', { target: 'Cash', values: ['Online Banking'] })).toBe(true);
28+
});
29+
30+
test('validates required_if with multiple comparison values', () => {
31+
// target matches one of the values
32+
expect(validate('', ['Online Banking', 'Online Banking', 'Wire Transfer'])).toBe(false);
33+
expect(validate('', ['Wire Transfer', 'Online Banking', 'Wire Transfer'])).toBe(false);
34+
expect(validate('Nabil', ['Online Banking', 'Online Banking', 'Wire Transfer'])).toBe(true);
35+
36+
// target matches none
37+
expect(validate('', ['Cash', 'Online Banking', 'Wire Transfer'])).toBe(true);
38+
});
39+
40+
test('validates required_if without comparison values (truthy target)', () => {
41+
// target is truthy, no comparison values -> field is required
42+
expect(validate('', ['some value'])).toBe(false);
43+
expect(validate('filled', ['some value'])).toBe(true);
44+
45+
// target is empty/falsy, no comparison values -> field is not required
46+
expect(validate('', [''])).toBe(true);
47+
expect(validate('', [null])).toBe(true);
48+
expect(validate('', [undefined])).toBe(true);
49+
});
50+
51+
test('validates required_if with object params and no values array', () => {
52+
// target is truthy -> required
53+
expect(validate('', { target: 'truthy' })).toBe(false);
54+
expect(validate('filled', { target: 'truthy' })).toBe(true);
55+
56+
// target is falsy -> not required
57+
expect(validate('', { target: '' })).toBe(true);
58+
expect(validate('', { target: null })).toBe(true);
59+
expect(validate('', { target: undefined })).toBe(true);
60+
});
61+
62+
test('uses loose equality for value comparison', () => {
63+
// '1' == 1 should be true with loose equality
64+
expect(validate('', ['1', 1])).toBe(false);
65+
expect(validate('', [1, '1'])).toBe(false);
66+
});

0 commit comments

Comments
 (0)