From 92efca1330e9cfcedca740e6ec54aaf32a39e5dc Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 4 Mar 2026 01:44:33 -0500 Subject: [PATCH] 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 --- .changeset/fix-4868-required-if-resubmit.md | 5 ++ packages/rules/src/index.ts | 3 + packages/rules/src/required_if.ts | 36 +++++++++++ packages/rules/tests/required_if.spec.ts | 66 +++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 .changeset/fix-4868-required-if-resubmit.md create mode 100644 packages/rules/src/required_if.ts create mode 100644 packages/rules/tests/required_if.spec.ts diff --git a/.changeset/fix-4868-required-if-resubmit.md b/.changeset/fix-4868-required-if-resubmit.md new file mode 100644 index 000000000..d3fbe8e20 --- /dev/null +++ b/.changeset/fix-4868-required-if-resubmit.md @@ -0,0 +1,5 @@ +--- +"@vee-validate/rules": patch +--- + +Fix required_if rule not re-evaluating on subsequent submissions (#4868) diff --git a/packages/rules/src/index.ts b/packages/rules/src/index.ts index 5d37a31f4..cb5258d5d 100644 --- a/packages/rules/src/index.ts +++ b/packages/rules/src/index.ts @@ -23,6 +23,7 @@ import numeric from './numeric'; import one_of from './one_of'; import regex from './regex'; import required from './required'; +import required_if from './required_if'; import size from './size'; import url from './url'; import { toTypedSchema } from './toTypedSchema'; @@ -54,6 +55,7 @@ export const all: Record> = { one_of, regex, required, + required_if, size, url, }; @@ -84,6 +86,7 @@ export { one_of, regex, required, + required_if, size, url, toTypedSchema, diff --git a/packages/rules/src/required_if.ts b/packages/rules/src/required_if.ts new file mode 100644 index 000000000..139a065a9 --- /dev/null +++ b/packages/rules/src/required_if.ts @@ -0,0 +1,36 @@ +import { isEmpty } from './utils'; +import { isEmptyArray, isNullOrUndefined } from '../../shared'; + +const requiredIfValidator = ( + value: unknown, + params: [unknown, ...unknown[]] | { target: unknown; values?: unknown[] }, +) => { + let target: unknown; + let values: unknown[] | undefined; + + if (Array.isArray(params)) { + target = params[0]; + values = params.slice(1); + } else { + target = params.target; + values = params.values; + } + + // Determine if the field should be required + let isRequired: boolean; + + if (values && values.length) { + // eslint-disable-next-line eqeqeq + isRequired = values.some(val => val == target); + } else { + isRequired = !isNullOrUndefined(target) && !isEmptyArray(target) && !!String(target).trim().length; + } + + if (!isRequired) { + return true; + } + + return !isEmpty(value); +}; + +export default requiredIfValidator; diff --git a/packages/rules/tests/required_if.spec.ts b/packages/rules/tests/required_if.spec.ts new file mode 100644 index 000000000..49076282d --- /dev/null +++ b/packages/rules/tests/required_if.spec.ts @@ -0,0 +1,66 @@ +import validate from '../src/required_if'; + +test('validates required_if with array params', () => { + // target has a matching value, field is empty -> invalid + expect(validate('', ['Online Banking', 'Online Banking'])).toBe(false); + expect(validate(null, ['Online Banking', 'Online Banking'])).toBe(false); + expect(validate(undefined, ['Online Banking', 'Online Banking'])).toBe(false); + expect(validate([], ['Online Banking', 'Online Banking'])).toBe(false); + + // target has a matching value, field is filled -> valid + expect(validate('Nabil', ['Online Banking', 'Online Banking'])).toBe(true); + + // target does not match any value, field is empty -> valid (not required) + expect(validate('', ['Cash', 'Online Banking'])).toBe(true); + expect(validate(null, ['Cash', 'Online Banking'])).toBe(true); + expect(validate(undefined, ['Cash', 'Online Banking'])).toBe(true); +}); + +test('validates required_if with object params', () => { + // target matches, field is empty -> invalid + expect(validate('', { target: 'Online Banking', values: ['Online Banking'] })).toBe(false); + + // target matches, field is filled -> valid + expect(validate('Nabil', { target: 'Online Banking', values: ['Online Banking'] })).toBe(true); + + // target does not match, field is empty -> valid + expect(validate('', { target: 'Cash', values: ['Online Banking'] })).toBe(true); +}); + +test('validates required_if with multiple comparison values', () => { + // target matches one of the values + expect(validate('', ['Online Banking', 'Online Banking', 'Wire Transfer'])).toBe(false); + expect(validate('', ['Wire Transfer', 'Online Banking', 'Wire Transfer'])).toBe(false); + expect(validate('Nabil', ['Online Banking', 'Online Banking', 'Wire Transfer'])).toBe(true); + + // target matches none + expect(validate('', ['Cash', 'Online Banking', 'Wire Transfer'])).toBe(true); +}); + +test('validates required_if without comparison values (truthy target)', () => { + // target is truthy, no comparison values -> field is required + expect(validate('', ['some value'])).toBe(false); + expect(validate('filled', ['some value'])).toBe(true); + + // target is empty/falsy, no comparison values -> field is not required + expect(validate('', [''])).toBe(true); + expect(validate('', [null])).toBe(true); + expect(validate('', [undefined])).toBe(true); +}); + +test('validates required_if with object params and no values array', () => { + // target is truthy -> required + expect(validate('', { target: 'truthy' })).toBe(false); + expect(validate('filled', { target: 'truthy' })).toBe(true); + + // target is falsy -> not required + expect(validate('', { target: '' })).toBe(true); + expect(validate('', { target: null })).toBe(true); + expect(validate('', { target: undefined })).toBe(true); +}); + +test('uses loose equality for value comparison', () => { + // '1' == 1 should be true with loose equality + expect(validate('', ['1', 1])).toBe(false); + expect(validate('', [1, '1'])).toBe(false); +});