From b075655c2d5e540ce2d860fd5f6c6e5729e371f8 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Fri, 23 Oct 2026 10:45:11 +0200 Subject: [PATCH 1/2] refactor: rename runOnSubmit to skipOnSubmit --- _artifacts/domain_map.yaml | 8 ++-- _artifacts/skill_spec.md | 2 +- docs/framework/angular/guides/validation.md | 2 +- docs/framework/lit/guides/validation.md | 2 +- docs/framework/preact/guides/validation.md | 2 +- docs/framework/react/guides/validation.md | 2 +- docs/framework/solid/guides/validation.md | 2 +- docs/framework/svelte/guides/validation.md | 2 +- docs/framework/vue/guides/validation.md | 2 +- docs/migrate-from-v1.md | 30 +++++++-------- .../src/app/shared-code.ts | 2 +- .../src/app/shared-code.ts | 2 +- examples/react/nextjs/app/shared-code.ts | 2 +- .../src/utils/form-isomorphic.ts | 2 +- packages/form-core/src/FormApi/FormApi.lib.ts | 2 +- packages/form-core/src/validation.public.ts | 36 +++++++++--------- .../form-core/src/validation/execution.lib.ts | 4 +- .../tests/FormApi/submission-handling.spec.ts | 28 +++++++------- .../tests/FormGroupApi/FormGroupApi.spec.ts | 6 +-- .../form-core/tests/serverValidate.spec.ts | 4 +- .../form-core/tests/serverValidate.test-d.ts | 4 +- .../tests/validation-output.test-d.ts | 10 ++--- .../form-core/tests/validation-output.test.ts | 18 ++++----- .../tests/validation-pipeline.test.ts | 18 ++++----- packages/form-core/tests/validation.test-d.ts | 38 +++++++++---------- .../tests/fieldDebugCases.test.ts | 4 +- .../preact-form/tests/FormGroup.test-d.tsx | 2 +- .../skills/reusable-form-policies/SKILL.md | 2 +- .../references/reusable-policy-boundaries.md | 2 +- .../skills/schema-driven-forms/SKILL.md | 6 +-- .../react-form/tests/FormGroup.test-d.tsx | 2 +- 31 files changed, 124 insertions(+), 124 deletions(-) diff --git a/_artifacts/domain_map.yaml b/_artifacts/domain_map.yaml index acee7ce01d..470362969c 100644 --- a/_artifacts/domain_map.yaml +++ b/_artifacts/domain_map.yaml @@ -433,7 +433,7 @@ skill_candidates: - 'ReactFormType splitting reduces nesting in one known form; AppForm reduces repeated field JSX; field groups cover sections reused across different forms or paths.' - 'Prefer form-level errorVisibility callbacks over repeated local showErrors checks.' - 'Use helper functions when a callback or validator config deserves a semantic name that preserves intent.' - - 'Use comments or JSDoc on named helpers to explain workflow behavior, trigger timing, debounce, bailIfInvalid, runOnSubmit, or rewardEarlyPunishLate policy.' + - 'Use comments or JSDoc on named helpers to explain workflow behavior, trigger timing, debounce, bailIfInvalid, skipOnSubmit, or rewardEarlyPunishLate policy.' - 'Keep reusable visibility policies value-agnostic unless they are intentionally tied to one form shape; use inline callbacks when typed values are required.' failure_modes: - name: split_form_type_written_by_hand @@ -455,7 +455,7 @@ skill_candidates: - packages/form-core/tests/validation.test-d.ts - examples/react/ui-integration-shadcn/src/app/booking/shared-form.tsx - name: duplicated_validator_config_without_intent - symptom: 'Trigger, debounce, runOnSubmit, or bailIfInvalid config is copied without explaining the intended workflow.' + symptom: 'Trigger, debounce, skipOnSubmit, or bailIfInvalid config is copied without explaining the intended workflow.' correction: 'Extract a named createValidator/createValidators helper and document the behavior intent.' evidence: - packages/form-core/tests/validation.test.ts @@ -589,7 +589,7 @@ skill_candidates: - packages/form-core/src/standardSchema.lib.ts - packages/form-core/tests/validation.test.ts - name: duplicated_validator_policy - symptom: 'The same triggers, debounce, runOnSubmit, or bailIfInvalid choices are copied across validators with no explanation.' + symptom: 'The same triggers, debounce, skipOnSubmit, or bailIfInvalid choices are copied across validators with no explanation.' correction: 'Extract a named createValidator/createValidators helper when the policy has semantic meaning, and document why that behavior exists.' evidence: - packages/form-core/tests/validation.test.ts @@ -705,7 +705,7 @@ skill_candidates: - 'Reusable policies can use createErrorVisibility but only receive unknown form values.' - 'Helper functions mainly serve to give a semantic name to a callback or validator config.' - 'Named reusable policies are good places for JSDoc or comments that explain why errors appear at that moment.' - - 'Reusable validator configs are useful when they explain trigger timing, bailIfInvalid, debounce, runOnSubmit, or rewardEarlyPunishLate behavior.' + - 'Reusable validator configs are useful when they explain trigger timing, bailIfInvalid, debounce, skipOnSubmit, or rewardEarlyPunishLate behavior.' - 'Inline callbacks can use typed form values from the consuming form.' - 'Visibility callbacks see pre-filter meta, not visible errors or validity.' - 'Group submission attempts are scoped to the nearest group.' diff --git a/_artifacts/skill_spec.md b/_artifacts/skill_spec.md index 90746a5ea0..ce1e85d5e1 100644 --- a/_artifacts/skill_spec.md +++ b/_artifacts/skill_spec.md @@ -128,7 +128,7 @@ Core guidance: - AppForm reduces repeated field JSX boilerplate; split sections reduce nesting in a known form; field groups cover shared sections across different forms or field paths. - Prefer form-level `errorVisibility` callbacks over scattered local patterns like `const showErrors = field.meta.isTouched && field.meta.isInvalid`. - Use helper functions mainly when a callback or validator config deserves a semantic name that preserves intent. -- Extract validator configs with `createValidator` or `createValidators` when trigger timing, debounce, `bailIfInvalid`, or `runOnSubmit` policy is complex enough to name. +- Extract validator configs with `createValidator` or `createValidators` when trigger timing, debounce, `bailIfInvalid`, or `skipOnSubmit` policy is complex enough to name. - Use comments or JSDoc on named helpers to explain behavior intent, such as why errors wait for blur or submit, or why a validator uses a `rewardEarlyPunishLate` policy. - Keep reusable policies value-agnostic unless they are intentionally tied to a specific form shape; inline callbacks are better when typed values are required. diff --git a/docs/framework/angular/guides/validation.md b/docs/framework/angular/guides/validation.md index a4a4b66cac..60a81a4957 100644 --- a/docs/framework/angular/guides/validation.md +++ b/docs/framework/angular/guides/validation.md @@ -56,7 +56,7 @@ Additional controls include: - `runOnMount`: run once when the form is constructed or when a field or form group first mounts. -- `runOnSubmit`: disable or conditionally enable submit-time execution. +- `skipOnSubmit`: conditionally skip submit-time execution. - `triggerDebounceMs`: debounce change and blur execution. - `bailIfInvalid`: skip this and subsequent validators when an earlier one failed. diff --git a/docs/framework/lit/guides/validation.md b/docs/framework/lit/guides/validation.md index 7529649bb0..5c8de4cb5e 100644 --- a/docs/framework/lit/guides/validation.md +++ b/docs/framework/lit/guides/validation.md @@ -55,7 +55,7 @@ Additional controls include: - `runOnMount`: run once when the form is constructed or when a field or form group first mounts. -- `runOnSubmit`: disable or conditionally enable submit-time execution. +- `skipOnSubmit`: conditionally skip submit-time execution. - `triggerDebounceMs`: debounce change and blur execution. - `bailIfInvalid`: skip this and subsequent validators when an earlier one failed. diff --git a/docs/framework/preact/guides/validation.md b/docs/framework/preact/guides/validation.md index af1754af7c..95aa5b7d0c 100644 --- a/docs/framework/preact/guides/validation.md +++ b/docs/framework/preact/guides/validation.md @@ -54,7 +54,7 @@ Additional controls include: - `runOnMount`: run once when the form is constructed or when a field or form group first mounts. -- `runOnSubmit`: disable or conditionally enable submit-time execution. +- `skipOnSubmit`: conditionally skip submit-time execution. - `triggerDebounceMs`: debounce change and blur execution. - `bailIfInvalid`: skip this and subsequent validators when an earlier one failed. diff --git a/docs/framework/react/guides/validation.md b/docs/framework/react/guides/validation.md index f4b7ac4d75..1c472d13bf 100644 --- a/docs/framework/react/guides/validation.md +++ b/docs/framework/react/guides/validation.md @@ -52,7 +52,7 @@ Additional controls include: - `runOnMount`: run once when the form is constructed or when a field or form group first mounts. -- `runOnSubmit`: disable or conditionally enable submit-time execution. +- `skipOnSubmit`: conditionally skip submit-time execution. - `triggerDebounceMs`: debounce change and blur execution. - `bailIfInvalid`: skip this and subsequent validators when an earlier one failed. diff --git a/docs/framework/solid/guides/validation.md b/docs/framework/solid/guides/validation.md index 2ad1e7eeda..c1bcd6a4d9 100644 --- a/docs/framework/solid/guides/validation.md +++ b/docs/framework/solid/guides/validation.md @@ -52,7 +52,7 @@ Additional controls include: - `runOnMount`: run once when the form is constructed or when a field or form group first mounts. -- `runOnSubmit`: disable or conditionally enable submit-time execution. +- `skipOnSubmit`: conditionally skip submit-time execution. - `triggerDebounceMs`: debounce change and blur execution. - `bailIfInvalid`: skip this and subsequent validators when an earlier one failed. diff --git a/docs/framework/svelte/guides/validation.md b/docs/framework/svelte/guides/validation.md index cb03e490b2..1a50925215 100644 --- a/docs/framework/svelte/guides/validation.md +++ b/docs/framework/svelte/guides/validation.md @@ -53,7 +53,7 @@ Additional controls include: - `runOnMount`: run once when the form is constructed or when a field or form group first mounts. -- `runOnSubmit`: disable or conditionally enable submit-time execution. +- `skipOnSubmit`: conditionally skip submit-time execution. - `triggerDebounceMs`: debounce change and blur execution. - `bailIfInvalid`: skip this and subsequent validators when an earlier one failed. diff --git a/docs/framework/vue/guides/validation.md b/docs/framework/vue/guides/validation.md index db45708e21..70e4c444c6 100644 --- a/docs/framework/vue/guides/validation.md +++ b/docs/framework/vue/guides/validation.md @@ -50,7 +50,7 @@ Additional controls include: - `runOnMount`: run once when the form is constructed or when a field or form group first mounts. -- `runOnSubmit`: disable or conditionally enable submit-time execution. +- `skipOnSubmit`: conditionally skip submit-time execution. - `triggerDebounceMs`: debounce change and blur execution. - `bailIfInvalid`: skip this and subsequent validators when an earlier one failed. diff --git a/docs/migrate-from-v1.md b/docs/migrate-from-v1.md index 703bbf1da9..e08030e2fa 100644 --- a/docs/migrate-from-v1.md +++ b/docs/migrate-from-v1.md @@ -278,20 +278,20 @@ bailing: Useful translations: -| v1 | v2 | -| ------------------------------------- | -------------------------------------------------------------------------------- | -| `validators.onMount` | validator with `runOnMount: true` | -| `validators.onChange` | validator with `triggers: ['change']` | -| `validators.onBlur` | validator with `triggers: ['blur']` | -| `validators.onSubmit` | validator with `triggers: []`, or rely on submit running validators by default | -| `onChangeAsync` | async `run` with `triggers: ['change']` | -| `onChangeAsyncDebounceMs` | `triggerDebounceMs` | -| `onChangeListenTo` / `onBlurListenTo` | `watchFields` | -| `validationLogic: revalidateLogic()` | migrate to explicit `triggers`, `when`, `runOnSubmit`, and `bailIfInvalid` rules | +| v1 | v2 | +| ------------------------------------- | --------------------------------------------------------------------------------- | +| `validators.onMount` | validator with `runOnMount: true` | +| `validators.onChange` | validator with `triggers: ['change']` | +| `validators.onBlur` | validator with `triggers: ['blur']` | +| `validators.onSubmit` | validator with `triggers: []`, or rely on submit running validators by default | +| `onChangeAsync` | async `run` with `triggers: ['change']` | +| `onChangeAsyncDebounceMs` | `triggerDebounceMs` | +| `onChangeListenTo` / `onBlurListenTo` | `watchFields` | +| `validationLogic: revalidateLogic()` | migrate to explicit `triggers`, `when`, `skipOnSubmit`, and `bailIfInvalid` rules | Submission is separate from the `triggers` list: every validator runs during submission by default. An empty `triggers: []` therefore means "no change or -blur validation, but still validate on submit." Set `runOnSubmit: false` when a +blur validation, but still validate on submit." Set `skipOnSubmit: true` when a validator must not run during client submission; do not add `'submit'` to `triggers`. @@ -317,7 +317,7 @@ validators={[ ``` Predicate callbacks are scope-aware. A `when` callback, a function-valued -`runOnSubmit`, or a function-valued `triggerDebounceMs` receives `scope` and +`skipOnSubmit`, or a function-valued `triggerDebounceMs` receives `scope` and `formApi`; field predicates receive the field being validated as `fieldApi`, while group predicates also receive `groupApi`. For form and group predicates, an optional `fieldApi` is the field that triggered validation. @@ -375,7 +375,7 @@ useForm({ The form's `value` remains its editable input state. When a Standard Schema validator runs successfully during submission, its parsed output is available in `onSubmit` at the corresponding `schemaOutputs` index. Non-schema entries -are `undefined`. Validators with literal `runOnSubmit: false` are skipped during +are `undefined`. Validators with literal `skipOnSubmit: true` are skipped during submission, and their slots are typed as `undefined`. Use the parsed entry when an endpoint expects the schema's output type. @@ -1000,7 +1000,7 @@ Key changes: - Share common form configuration with `formOptions(...)`. - Return the `createErrorMap()` map directly from framework/server validators when routing form and field errors. -- Combine `triggers: ['server']` with `runOnSubmit: false` for a form validator +- Combine `triggers: ['server']` with `skipOnSubmit: true` for a form validator that must run only on the server. Server-triggered validators otherwise still run during client submission because submission is enabled by default. - Next.js and Remix adapters, and the Start adapter when no callback override is @@ -1029,7 +1029,7 @@ In `formOpts`, a server-only validator returns its routed error map directly: ```tsx { triggers: ['server'], - runOnSubmit: false, + skipOnSubmit: true, run: ({ value, createErrorMap }) => { const errors = createErrorMap() diff --git a/examples/react/next-server-actions-zod/src/app/shared-code.ts b/examples/react/next-server-actions-zod/src/app/shared-code.ts index 629e8b59c8..f0a63eb093 100644 --- a/examples/react/next-server-actions-zod/src/app/shared-code.ts +++ b/examples/react/next-server-actions-zod/src/app/shared-code.ts @@ -20,7 +20,7 @@ export const formOpts = formOptions.strictSchema(clientSchema, { }, { triggers: ['server'], - runOnSubmit: false, + skipOnSubmit: true, run: serverSchema, }, ], diff --git a/examples/react/next-server-actions/src/app/shared-code.ts b/examples/react/next-server-actions/src/app/shared-code.ts index 9d557bfa7f..e5b8a0fead 100644 --- a/examples/react/next-server-actions/src/app/shared-code.ts +++ b/examples/react/next-server-actions/src/app/shared-code.ts @@ -20,7 +20,7 @@ export const formOpts = formOptions({ }, { triggers: ['server'], - runOnSubmit: false, + skipOnSubmit: true, run: ({ value, createErrorMap }) => { const errors = createErrorMap() diff --git a/examples/react/nextjs/app/shared-code.ts b/examples/react/nextjs/app/shared-code.ts index 077402436d..7c75fd1b93 100644 --- a/examples/react/nextjs/app/shared-code.ts +++ b/examples/react/nextjs/app/shared-code.ts @@ -21,7 +21,7 @@ export const formOpts = formOptions({ { triggers: ['server'], // This validator will only run on the server - runOnSubmit: false, + skipOnSubmit: true, run: ({ value, createErrorMap }) => { const errors = createErrorMap() diff --git a/examples/react/tanstack-start/src/utils/form-isomorphic.ts b/examples/react/tanstack-start/src/utils/form-isomorphic.ts index 077402436d..7c75fd1b93 100644 --- a/examples/react/tanstack-start/src/utils/form-isomorphic.ts +++ b/examples/react/tanstack-start/src/utils/form-isomorphic.ts @@ -21,7 +21,7 @@ export const formOpts = formOptions({ { triggers: ['server'], // This validator will only run on the server - runOnSubmit: false, + skipOnSubmit: true, run: ({ value, createErrorMap }) => { const errors = createErrorMap() diff --git a/packages/form-core/src/FormApi/FormApi.lib.ts b/packages/form-core/src/FormApi/FormApi.lib.ts index 4096aca4b3..c3b9a3e87a 100644 --- a/packages/form-core/src/FormApi/FormApi.lib.ts +++ b/packages/form-core/src/FormApi/FormApi.lib.ts @@ -1183,7 +1183,7 @@ export class InternalFormApi< * -> { * run: () => {}, * triggers: ['change', 'blur', 'submit', 'mount', 'unmount'], // not the same as submit - * // runOnSubmit doesn't exist + * // skipOnSubmit doesn't exist * listenToFields: ['otherField'] * } * diff --git a/packages/form-core/src/validation.public.ts b/packages/form-core/src/validation.public.ts index d4651aa696..b997866b1a 100644 --- a/packages/form-core/src/validation.public.ts +++ b/packages/form-core/src/validation.public.ts @@ -48,12 +48,12 @@ export interface Validator< /** * Whether this validator runs during a submission attempt, independently of `triggers`. * - * Pass a boolean to enable or disable it, or pass a function that returns - * `true` to enable it for the current attempt. + * Pass a boolean directly, or pass a function that returns + * `true` to skip it for the current attempt. * - * @default true + * @default false */ - runOnSubmit?: + skipOnSubmit?: boolean | ValidationPredicateFn /** * Whether this validator runs when the form is constructed or when its field @@ -77,7 +77,7 @@ export interface Validator< * Change and blur events that can run this validator, optionally with conditions. * * An empty array disables change and blur validation. Configure submission - * separately with `runOnSubmit` and initial validation with `runOnMount`. + * separately with `skipOnSubmit` and initial validation with `runOnMount`. */ triggers: Array< ValidationTriggerOption @@ -255,7 +255,7 @@ export type ValidationTrigger = 'change' | 'blur' | 'submit' export type ServerValidationTrigger = 'server' /** Events reported to client-side validation callbacks. */ export type ClientValidationTrigger = ValidationTrigger -/** Events configured through `triggers`. Submission is controlled by `runOnSubmit`. */ +/** Events configured through `triggers`. Submission is controlled by `skipOnSubmit`. */ export type ConfigurableValidationTrigger = Exclude /** Change and blur events accepted by shared validator configurations. */ export type ValidatorTrigger = ConfigurableValidationTrigger @@ -559,7 +559,7 @@ export type ClientValidationTriggerOption< * A client event with an optional condition, or the `'server'` event for a form validator. * * Server validation selects validators with a `'server'` trigger independently - * of `runOnSubmit`. + * of `skipOnSubmit`. * * @typeParam TFormData - Library-managed. Do not specify explicitly. * @typeParam TValue - Library-managed. Do not specify explicitly. @@ -819,12 +819,12 @@ export interface FormValidator extends BaseValidator< /** * Whether this validator runs during a client submission attempt, independently of `triggers`. * - * Pass a boolean to enable or disable it, or pass a function that returns - * `true` to enable it for the current attempt. + * Pass a boolean or a function that returns + * `true` to skip it for the current attempt. * - * @default true + * @default false */ - runOnSubmit?: boolean | ValidationPredicateFn + skipOnSubmit?: boolean | ValidationPredicateFn /** * Whether this validator runs when the form is constructed. * @@ -847,7 +847,7 @@ export interface FormValidator extends BaseValidator< * Add `'server'` to include it in `serverValidate`. * * An empty array disables change, blur, and server validation. Configure client - * submission separately with `runOnSubmit` and initial validation with `runOnMount`. + * submission separately with `skipOnSubmit` and initial validation with `runOnMount`. */ triggers: Array> } @@ -1192,12 +1192,12 @@ type TryGetOutput = TValidator extends { type TryGetSubmitOutput = TryGetOutput extends infer TOutput - ? TValidator extends { readonly runOnSubmit: infer TRunOnSubmit } - ? [TRunOnSubmit] extends [false] - ? undefined // User explicitly set runOnSubmit: false -> guaranteed undefined - : TRunOnSubmit extends (...args: Array) => boolean + ? TValidator extends { readonly skipOnSubmit: infer TSkipOnSubmit } + ? [TSkipOnSubmit] extends [true] + ? undefined // User explicitly set skipOnSubmit: false -> guaranteed undefined + : TSkipOnSubmit extends (...args: Array) => boolean ? TOutput | undefined // Callback could dynamically be true or false -> union - : false extends TRunOnSubmit + : true extends TSkipOnSubmit ? TOutput | undefined // the explicit variable is boolean, so also dynamic -> union : TOutput : TOutput // default, which is guaranteed present @@ -1382,7 +1382,7 @@ type MappedServerOutputs> = { * Infers server validation output slots in form validator configuration order. * * Validators without a `'server'` trigger have `undefined` slots. Server - * selection is independent of `runOnSubmit`. + * selection is independent of `skipOnSubmit`. * * @typeParam TFormValidators - Library-managed. Do not specify explicitly. */ diff --git a/packages/form-core/src/validation/execution.lib.ts b/packages/form-core/src/validation/execution.lib.ts index a7e1ba92f2..b46ba54f3e 100644 --- a/packages/form-core/src/validation/execution.lib.ts +++ b/packages/form-core/src/validation/execution.lib.ts @@ -230,10 +230,10 @@ export function shouldRunValidator( return hasServerTrigger(validator) } - const { runOnSubmit = true } = validator + const { skipOnSubmit = false } = validator if (context.event === 'submit') { - return getEnabledState(runOnSubmit, context) + return !getEnabledState(skipOnSubmit, context) } return validator.triggers.some((signal) => diff --git a/packages/form-core/tests/FormApi/submission-handling.spec.ts b/packages/form-core/tests/FormApi/submission-handling.spec.ts index b66e2bd3e0..1e3a4a5ab0 100644 --- a/packages/form-core/tests/FormApi/submission-handling.spec.ts +++ b/packages/form-core/tests/FormApi/submission-handling.spec.ts @@ -480,14 +480,14 @@ describe('form - submission handling', () => { }) it('clears prior schema output when a dynamic submit predicate skips the validator', async () => { - let shouldRunOnSubmit = true + let shouldSkipSubmit = false const onSubmit = vi.fn() const form = new InternalFormApi({ defaultValues: { name: 'test' }, validators: [ { run: z.object({ name: z.string() }), - runOnSubmit: () => shouldRunOnSubmit, + skipOnSubmit: () => shouldSkipSubmit, triggers: [], }, ], @@ -499,7 +499,7 @@ describe('form - submission handling', () => { expect.objectContaining({ validatorOutputs: [{ name: 'test' }] }), ) - shouldRunOnSubmit = false + shouldSkipSubmit = true await form.handleSubmit() expect(onSubmit).toHaveBeenLastCalledWith( @@ -592,7 +592,7 @@ describe('form - submission handling', () => { expect(thirdValidatorFn).not.toHaveBeenCalled() }) - it('skips validators with runOnSubmit: false', async () => { + it('skips validators with skipOnSubmit: true', async () => { const validatorFn = vi .fn() .mockImplementation(() => ({ message: 'Should not run' })) @@ -600,7 +600,7 @@ describe('form - submission handling', () => { defaultValues: { name: '' }, validators: [ { - runOnSubmit: false, + skipOnSubmit: true, run: validatorFn, triggers: ['change'], }, @@ -611,7 +611,7 @@ describe('form - submission handling', () => { expect(validatorFn).not.toHaveBeenCalled() }) - it('runs validators with runOnSubmit: true on submit', async () => { + it('runs validators with skipOnSubmit: false on submit', async () => { const validatorFn = vi .fn() .mockImplementation(() => ({ message: 'Submit error' })) @@ -619,7 +619,7 @@ describe('form - submission handling', () => { defaultValues: { name: '' }, validators: [ { - runOnSubmit: true, + skipOnSubmit: false, run: validatorFn, triggers: [], }, @@ -630,7 +630,7 @@ describe('form - submission handling', () => { expect(validatorFn).toHaveBeenCalled() }) - it('runs validators with runOnSubmit: false on their triggers', async () => { + it('runs validators with skipOnSubmit: true on their triggers', async () => { const validatorFn = vi .fn() .mockImplementation(() => ({ message: 'Change error' })) @@ -638,19 +638,19 @@ describe('form - submission handling', () => { defaultValues: { name: '' }, validators: [ { - runOnSubmit: false, + skipOnSubmit: true, run: validatorFn, triggers: ['change'], }, ], }) await form.validate('change') - // runOnSubmit: false only affects submit, not the configured triggers + // skipOnSubmit: true only affects submit, not the configured triggers expect(form.state.errors).toEqual([{ message: 'Change error' }]) expect(validatorFn).toHaveBeenCalled() }) - it('skips validators with runOnSubmit: false and no triggers on submit', async () => { + it('skips validators with skipOnSubmit: true and no triggers on submit', async () => { const validatorFn = vi .fn() .mockImplementation(() => ({ message: 'Should not run' })) @@ -658,7 +658,7 @@ describe('form - submission handling', () => { defaultValues: { name: '' }, validators: [ { - runOnSubmit: false, + skipOnSubmit: true, run: validatorFn, triggers: [], }, @@ -669,7 +669,7 @@ describe('form - submission handling', () => { expect(validatorFn).not.toHaveBeenCalled() }) - it('supports runOnSubmit as a function', async () => { + it('supports skipOnSubmit as a function', async () => { const validatorFn = vi .fn() .mockImplementation(() => ({ message: 'Conditional submit error' })) @@ -677,7 +677,7 @@ describe('form - submission handling', () => { defaultValues: { name: '' }, validators: [ { - runOnSubmit: ({ formApi }) => formApi.state.values.name === '', + skipOnSubmit: ({ formApi }) => formApi.state.values.name !== '', run: validatorFn, triggers: [], }, diff --git a/packages/form-core/tests/FormGroupApi/FormGroupApi.spec.ts b/packages/form-core/tests/FormGroupApi/FormGroupApi.spec.ts index 74c906f0ff..5b7b0a5267 100644 --- a/packages/form-core/tests/FormGroupApi/FormGroupApi.spec.ts +++ b/packages/form-core/tests/FormGroupApi/FormGroupApi.spec.ts @@ -889,7 +889,7 @@ describe('FormGroupApi', () => { }) it('clears prior group schema output when a dynamic submit predicate skips the validator', async () => { - let shouldRunOnSubmit = true + let shouldSkipSubmit = false const onSubmit = vi.fn() const form = new InternalFormApi({ defaultValues: { guestDetails: { name: 'Tony' } }, @@ -900,7 +900,7 @@ describe('FormGroupApi', () => { validators: [ { run: z.object({ name: z.string() }), - runOnSubmit: () => shouldRunOnSubmit, + skipOnSubmit: () => shouldSkipSubmit, triggers: [], }, ], @@ -912,7 +912,7 @@ describe('FormGroupApi', () => { expect.objectContaining({ validatorOutputs: [{ name: 'Tony' }] }), ) - shouldRunOnSubmit = false + shouldSkipSubmit = true await group.handleSubmit() expect(onSubmit).toHaveBeenLastCalledWith( diff --git a/packages/form-core/tests/serverValidate.spec.ts b/packages/form-core/tests/serverValidate.spec.ts index 5a6bb81f1a..2a8c348da3 100644 --- a/packages/form-core/tests/serverValidate.spec.ts +++ b/packages/form-core/tests/serverValidate.spec.ts @@ -149,7 +149,7 @@ describe('server validation', () => { expect(serverValidator).toHaveBeenCalledOnce() }) - it('skips server-only validators during client submit when runOnSubmit is false', async () => { + it('skips server-only validators during client submit when skipOnSubmit is true', async () => { const validator = vi.fn(() => 'Submit error') const form = new InternalFormApi( formOptions({ @@ -158,7 +158,7 @@ describe('server validation', () => { { run: validator, triggers: ['server'], - runOnSubmit: false, + skipOnSubmit: true, }, ], }), diff --git a/packages/form-core/tests/serverValidate.test-d.ts b/packages/form-core/tests/serverValidate.test-d.ts index a126b87415..8fca505c90 100644 --- a/packages/form-core/tests/serverValidate.test-d.ts +++ b/packages/form-core/tests/serverValidate.test-d.ts @@ -239,11 +239,11 @@ describe('server validation types', () => { >() return undefined }, - runOnSubmit: ({ formApi }) => { + skipOnSubmit: ({ formApi }) => { expectTypeOf(formApi.state.values).toEqualTypeOf<{ name: string }>() - return true + return false }, triggerDebounceMs: ({ formApi }) => { expectTypeOf(formApi.state.values).toEqualTypeOf<{ diff --git a/packages/form-core/tests/validation-output.test-d.ts b/packages/form-core/tests/validation-output.test-d.ts index 1f743f8e39..24c09cd0ac 100644 --- a/packages/form-core/tests/validation-output.test-d.ts +++ b/packages/form-core/tests/validation-output.test-d.ts @@ -215,22 +215,22 @@ describe('validator output types', () => { { run: ({ createOutput }) => createOutput(1), triggers: [], - runOnSubmit: false, + skipOnSubmit: true, }, { run: ({ createOutput }) => createOutput(1), triggers: [], - runOnSubmit: true as boolean, + skipOnSubmit: false as boolean, }, { run: ({ createOutput }) => createOutput(1), triggers: [], - runOnSubmit: () => true, + skipOnSubmit: () => false, }, { run: ({ createOutput }) => createOutput(1), triggers: [], - runOnSubmit: true, + skipOnSubmit: false, }, ]) @@ -274,7 +274,7 @@ describe('validator output types', () => { run: async ({ value, createOutput }) => createOutput(value.name.length), triggers: ['server'], - runOnSubmit: false, + skipOnSubmit: true, }, ], }) diff --git a/packages/form-core/tests/validation-output.test.ts b/packages/form-core/tests/validation-output.test.ts index 6dac7bd53f..c9f11e95e3 100644 --- a/packages/form-core/tests/validation-output.test.ts +++ b/packages/form-core/tests/validation-output.test.ts @@ -67,7 +67,7 @@ describe('validator outputs', () => { triggers: [], }, { run: () => undefined, triggers: [] }, - { run: skipped, triggers: [], runOnSubmit: false }, + { run: skipped, triggers: [], skipOnSubmit: true }, ], onSubmit, }), @@ -119,7 +119,7 @@ describe('validator outputs', () => { }) it('clears earlier outputs when a later submission skips or succeeds without output', async () => { - let shouldRun = true + let shouldSkip = false let produceOutput = true const onSubmit = vi.fn() const form = new InternalFormApi( @@ -129,7 +129,7 @@ describe('validator outputs', () => { { run: ({ value, createOutput }) => produceOutput ? createOutput(value.name) : [], - runOnSubmit: () => shouldRun, + skipOnSubmit: () => shouldSkip, triggers: [], }, ], @@ -142,13 +142,13 @@ describe('validator outputs', () => { expect.objectContaining({ validatorOutputs: ['Tony'] }), ) - shouldRun = false + shouldSkip = true await form.handleSubmit() expect(onSubmit).toHaveBeenLastCalledWith( expect.objectContaining({ validatorOutputs: [undefined] }), ) - shouldRun = true + shouldSkip = false await form.handleSubmit() expect(onSubmit).toHaveBeenLastCalledWith( expect.objectContaining({ validatorOutputs: ['Tony'] }), @@ -171,7 +171,7 @@ describe('validator outputs', () => { { run: ({ createOutput }) => createOutput('event output'), triggers: ['change', 'blur'], - runOnSubmit: false, + skipOnSubmit: true, }, { run: nextValidator, @@ -212,7 +212,7 @@ describe('validator outputs', () => { ? Promise.resolve(createOutput('mount output')) : createOutput('mount output'), runOnMount: true, - runOnSubmit: false, + skipOnSubmit: true, triggers: [], }, { @@ -257,7 +257,7 @@ describe('validator outputs', () => { run: async ({ value, createOutput }) => createOutput({ name: value.name.trim() }), runOnMount: true, - runOnSubmit: () => shouldRun, + skipOnSubmit: () => !shouldRun, triggers: ['change'], }, ], @@ -296,7 +296,7 @@ describe('validator outputs', () => { run: async ({ value, createOutput }) => createOutput(value.name.length), triggers: ['server'], - runOnSubmit: false, + skipOnSubmit: true, }, ], }) diff --git a/packages/form-core/tests/validation-pipeline.test.ts b/packages/form-core/tests/validation-pipeline.test.ts index dbff7c12b9..4212867cce 100644 --- a/packages/form-core/tests/validation-pipeline.test.ts +++ b/packages/form-core/tests/validation-pipeline.test.ts @@ -311,22 +311,22 @@ describe('runFormValidatorPipeline', () => { }, { run: trueCallback, - runOnSubmit: () => true, + skipOnSubmit: () => false, triggers: [], }, { run: trueBoolean, - runOnSubmit: true, + skipOnSubmit: false, triggers: [], }, { run: falseBoolean, - runOnSubmit: false, + skipOnSubmit: true, triggers: [], }, { run: falseCallback, - runOnSubmit: () => false, + skipOnSubmit: () => true, triggers: [], }, ]) @@ -1056,7 +1056,7 @@ describe('runFormValidatorPipeline', () => { it('aborts pending schema output before a dynamic predicate skips the validator', async () => { const formApi = getForm({ name: 'stale' }) - let shouldRunOnSubmit = true + let shouldSkipOnSubmit = false let resolveSchema!: (result: { value: { name: string } }) => void const output = new Promise<{ value: { name: string } }>((resolve) => { resolveSchema = resolve @@ -1071,13 +1071,13 @@ describe('runFormValidatorPipeline', () => { const { runWithContext, pipeline } = getPipeline(formApi, [ { run: schema, - runOnSubmit: () => shouldRunOnSubmit, + skipOnSubmit: () => shouldSkipOnSubmit, triggers: [], }, ]) const staleValidation = runWithContext({ event: 'submit' }) - shouldRunOnSubmit = false + shouldSkipOnSubmit = true await runWithContext({ event: 'submit' }) await staleValidation @@ -1282,7 +1282,7 @@ describe('runFormValidatorPipeline', () => { ]) }) - it('should not run zod schema validators with runOnSubmit disabled', async () => { + it('should not run zod schema validators with skipOnSubmit enabled', async () => { const formApi = getForm({ name: '' }) const schema = z.object({ @@ -1293,7 +1293,7 @@ describe('runFormValidatorPipeline', () => { { run: schema, triggers: [], - runOnSubmit: false, + skipOnSubmit: true, }, ]) diff --git a/packages/form-core/tests/validation.test-d.ts b/packages/form-core/tests/validation.test-d.ts index fd5f630260..7adec17de0 100644 --- a/packages/form-core/tests/validation.test-d.ts +++ b/packages/form-core/tests/validation.test-d.ts @@ -801,7 +801,7 @@ describe('FormErrors', () => { when: ({ formApi }) => formApi.state.submissionAttempts > 0, }, ], - runOnSubmit: true, + skipOnSubmit: false, }) const form = new InternalFormApi({ @@ -826,7 +826,7 @@ describe('FormErrors', () => { when: ({ formApi }) => formApi.state.submissionAttempts > 0, }, ], - runOnSubmit: true, + skipOnSubmit: false, }, { triggers: [ @@ -835,7 +835,7 @@ describe('FormErrors', () => { when: ({ formApi }) => formApi.state.submissionAttempts > 0, }, ], - runOnSubmit: true, + skipOnSubmit: false, bailIfInvalid: true, }, ]) @@ -1527,7 +1527,7 @@ describe('validator type transforms', () => { >() }) - it('it should transform based on runOnSubmit', () => { + it('it should transform based on skipOnSubmit', () => { const vs = defineFormValidators([ { run: z.object({ name: z.string() }), @@ -1536,12 +1536,12 @@ describe('validator type transforms', () => { { run: z.object({ name: z.string() }), triggers: [], - runOnSubmit: false, + skipOnSubmit: true, }, { run: z.object({ name: z.string() }), triggers: [], - runOnSubmit: true, + skipOnSubmit: false, }, { run: z.object({ name: z.string() }), @@ -1550,7 +1550,7 @@ describe('validator type transforms', () => { { run: z.object({ name: z.string() }), triggers: ['server'], - runOnSubmit: true, + skipOnSubmit: false, }, { run: z.object({ name: z.string() }), @@ -1562,7 +1562,7 @@ describe('validator type transforms', () => { expectTypeOf().toEqualTypeOf< readonly [ - // Assume runOnSubmit is true if omitted. + // Assume skipOnSubmit is true if omitted. { name: string }, // It was explicitly set false. undefined, @@ -1575,8 +1575,8 @@ describe('validator type transforms', () => { >() }) - it('makes schema outputs optional when runOnSubmit is dynamic', () => { - const dynamicRunOnSubmit = true as boolean + it('makes schema outputs optional when skipOnSubmit is dynamic', () => { + const dynamicSkipOnSubmit = false as boolean const vs = defineFormValidators([ { run: z.object({ name: z.string() }), @@ -1585,44 +1585,44 @@ describe('validator type transforms', () => { { run: z.object({ name: z.string() }), triggers: [], - runOnSubmit: true, + skipOnSubmit: false, }, { run: z.object({ name: z.string() }), triggers: [], - runOnSubmit: false, + skipOnSubmit: true, }, { run: z.object({ name: z.string() }), triggers: [], - runOnSubmit: dynamicRunOnSubmit, + skipOnSubmit: dynamicSkipOnSubmit, }, { run: z.object({ name: z.string() }), triggers: [], - runOnSubmit: () => true, + skipOnSubmit: () => true, }, { run: z.object({ name: z.string() }), triggers: [], - runOnSubmit: () => false, + skipOnSubmit: () => false, }, { run: z.object({ name: z.string() }), triggers: [], - runOnSubmit: () => dynamicRunOnSubmit, + skipOnSubmit: () => dynamicSkipOnSubmit, }, { run: z.object({ name: z.string() }), triggers: [], - runOnSubmit: undefined, + skipOnSubmit: undefined, }, ]) type Output = { name: string } type Outputs = ToFormOutputs type Expected = readonly [ - // An omitted runOnSubmit defaults to true, so the output is guaranteed. + // An omitted skipOnSubmit defaults to false, so the output is guaranteed. Output, // A literal true always runs the validator during submit. Output, @@ -1690,7 +1690,7 @@ describe('validator type transforms', () => { }, { run: schema, - runOnSubmit: false, + skipOnSubmit: true, triggers: [], }, ]) diff --git a/packages/form-devtools/tests/fieldDebugCases.test.ts b/packages/form-devtools/tests/fieldDebugCases.test.ts index cde9a5679c..5d827294fe 100644 --- a/packages/form-devtools/tests/fieldDebugCases.test.ts +++ b/packages/form-devtools/tests/fieldDebugCases.test.ts @@ -46,7 +46,7 @@ describe('field debug cases', () => { { ...emptyTriggerValidator, runOnMount: true, - runOnSubmit: false, + skipOnSubmit: true, }, ] as never, }) @@ -79,7 +79,7 @@ describe('field debug cases', () => { { ...emptyTriggerValidator, runOnMount: true, - runOnSubmit: false, + skipOnSubmit: true, }, ] as never, }) diff --git a/packages/preact-form/tests/FormGroup.test-d.tsx b/packages/preact-form/tests/FormGroup.test-d.tsx index a7d27188d3..de378876f6 100644 --- a/packages/preact-form/tests/FormGroup.test-d.tsx +++ b/packages/preact-form/tests/FormGroup.test-d.tsx @@ -166,7 +166,7 @@ function FormGroupSubmitTypes() { { run: guestDetailsSchema, triggers: [] }, { run: guestDetailsSchema, - runOnSubmit: false, + skipOnSubmit: true, triggers: [], }, ]} diff --git a/packages/react-form/skills/reusable-form-policies/SKILL.md b/packages/react-form/skills/reusable-form-policies/SKILL.md index a543ac8d94..aacae94a23 100644 --- a/packages/react-form/skills/reusable-form-policies/SKILL.md +++ b/packages/react-form/skills/reusable-form-policies/SKILL.md @@ -124,7 +124,7 @@ export const validateAfterBlurThenWhileInvalid = createValidator({ }) ``` -Use helper configs when trigger timing, debounce, `bailIfInvalid`, or `runOnSubmit` is a reusable policy. +Use helper configs when trigger timing, debounce, `bailIfInvalid`, or `skipOnSubmit` is a reusable policy. ## Common Mistakes diff --git a/packages/react-form/skills/reusable-form-policies/references/reusable-policy-boundaries.md b/packages/react-form/skills/reusable-form-policies/references/reusable-policy-boundaries.md index e42d6f60be..b1ece333f2 100644 --- a/packages/react-form/skills/reusable-form-policies/references/reusable-policy-boundaries.md +++ b/packages/react-form/skills/reusable-form-policies/references/reusable-policy-boundaries.md @@ -8,4 +8,4 @@ Use field groups when a section must work across different forms or paths. Do no Use `createErrorVisibility` when the visibility policy can be form-agnostic. The reusable callback sees `state.values` as `unknown`. Inline `errorVisibility` is better when the callback needs typed values from one specific form. -Use `createValidator` or `createValidators` when timing is the policy: `triggers`, `triggerDebounceMs`, `bailIfInvalid`, `runOnSubmit`, or a named workflow such as reward early and punish late. +Use `createValidator` or `createValidators` when timing is the policy: `triggers`, `triggerDebounceMs`, `bailIfInvalid`, `skipOnSubmit`, or a named workflow such as reward early and punish late. diff --git a/packages/react-form/skills/schema-driven-forms/SKILL.md b/packages/react-form/skills/schema-driven-forms/SKILL.md index 9df0b55af3..7cb2d4fd54 100644 --- a/packages/react-form/skills/schema-driven-forms/SKILL.md +++ b/packages/react-form/skills/schema-driven-forms/SKILL.md @@ -3,7 +3,7 @@ name: schema-driven-forms description: > Use when wiring Standard Schema-compatible schemas into TanStack React Form v2 validators, validatorOutputs, parseIssues, createValidationError, field errors, - nested issue paths, runOnSubmit behavior, and typed submit output. Load after + nested issue paths, skipOnSubmit behavior, and typed submit output. Load after choosing form option mode for schema-driven React forms. metadata: type: framework @@ -120,11 +120,11 @@ Use `parseIssues` when an endpoint or local schema check returns Standard Schema const validateWhileEditing = { run: profileSchema, triggers: ['change'], - runOnSubmit: false, + skipOnSubmit: true, } ``` -`runOnSubmit: false` means that validator does not contribute a parsed submit output. +`skipOnSubmit: true` means that validator does not contribute a parsed submit output. ## Common Mistakes diff --git a/packages/react-form/tests/FormGroup.test-d.tsx b/packages/react-form/tests/FormGroup.test-d.tsx index 01b14972d8..78a3548098 100644 --- a/packages/react-form/tests/FormGroup.test-d.tsx +++ b/packages/react-form/tests/FormGroup.test-d.tsx @@ -166,7 +166,7 @@ function FormGroupSubmitTypes() { { run: guestDetailsSchema, triggers: [] }, { run: guestDetailsSchema, - runOnSubmit: false, + skipOnSubmit: true, triggers: [], }, ]} From 43ccfa629cc547f54fd6ff8f3511ef58388962db Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Fri, 23 Oct 2026 10:46:44 +0200 Subject: [PATCH 2/2] chore: add changeset --- .changeset/flat-rats-like.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/flat-rats-like.md diff --git a/.changeset/flat-rats-like.md b/.changeset/flat-rats-like.md new file mode 100644 index 0000000000..4c118e362b --- /dev/null +++ b/.changeset/flat-rats-like.md @@ -0,0 +1,13 @@ +--- +'@tanstack/form-core': patch +'@tanstack/form-devtools': patch +'@tanstack/preact-form': patch +'@tanstack/react-form': patch +--- + +BREAKING: `runOnSubmit` has been renamed in favor of `skipOnSubmit`. That way, it's easier to explain what happens when you omit the property. + +Migration: + +- rename `runOnSubmit` to `skipOnSubmit` +- invert the condition you returned.