Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changeset/grumpy-seals-dig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'@tanstack/angular-form': minor
'@tanstack/form-core': minor
'@tanstack/preact-form': minor
'@tanstack/react-form': minor
---

BREAKING: `schemaOutputs` no longer exists. It is superceded by `validatorOutputs`. This is only a name change, the schema's result is the same as before

`validatorOutputs` now allows a callback validator to specify data that the submit function should know. Schemas do this
automatically, but now you can do it manually as well:
`run: ({ createOutput }) => createOutput('This is not a validation error and onSubmit can use this')`

The current map is:

| Validator | `onSubmit: ({ validatorOutputs }) => ...` |
| -------------------------- | ----------------------------------------- |
| Schema | Schema's output data |
| `createOutput(X)` | `X` |
| Default callback validator | `undefined` |
| Validation error | N/A, `onSubmit` doesn't run |
18 changes: 9 additions & 9 deletions _artifacts/domain_map.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ maintainer_guidance:
properties:
- 'defaultValues must align with the schema input type and should produce type errors when they do not.'
- 'Callbacks may not have useful inference unless a schema is present in the validator array.'
- 'onSubmit should read the transform output from schemaOutputs instead of raw value.'
- 'onSubmit should read the transform output from validatorOutputs instead of raw value.'
loose_schema:
summary: 'Schema-source ruleset mode with editable defaults.'
use_when:
Expand All @@ -54,7 +54,7 @@ maintainer_guidance:
- 'The input is looser while the parsed submit output is still the schema output type.'
- 'defaultValues must still align with the schema shape, but properties may also be null or undefined where editing requires it.'
- 'Callbacks may not have useful inference unless a schema is present in the validator array.'
- 'onSubmit should read the transform output from schemaOutputs instead of raw value.'
- 'onSubmit should read the transform output from validatorOutputs instead of raw value.'
schema_ecosystem:
standard_schema:
role: 'Most common integration shape.'
Expand Down Expand Up @@ -247,7 +247,7 @@ domains:
- '@tanstack/form-core'
core_questions:
- 'Is the schema a pipeline from input A to output B, or a ruleset for confirming B?'
- 'Should submit logic read raw value or schemaOutputs?'
- 'Should submit logic read raw value or validatorOutputs?'
- 'Do defaults need to be nullable or maybe undefined while editing?'
related_skills:
- choosing-form-options-modes
Expand Down Expand Up @@ -492,7 +492,7 @@ skill_candidates:
- 'looseSchema uses the schema as a ruleset while allowing editable default values that include null or undefined where needed.'
- 'looseSchema defaultValues must still align with the schema shape, but the input can be looser while output remains schema output.'
- 'In schema modes, callbacks may not infer usefully unless a schema is present in the validator array.'
- 'In schema modes, onSubmit should read schemaOutputs instead of raw value.'
- 'In schema modes, onSubmit should read validatorOutputs instead of raw value.'
- 'Use Standard Schema-capable libraries as heuristic examples only: Zod generally leans ruleset/firewall, Valibot generally leans pipeline, ArkType is mixed, and Effect Schema often leans pipeline/codec.'
failure_modes:
- name: expecting_option_helper_runtime_behavior
Expand Down Expand Up @@ -527,13 +527,13 @@ skill_candidates:
- packages/form-core/tests/validation.test-d.ts
- name: loose_pipeline_schema_loses_transform_intent
symptom: 'Submit logic treats raw form value as if it were the transformed schema output.'
correction: 'Use strictSchema for pipeline schemas and read the transformed value from schemaOutputs.'
correction: 'Use strictSchema for pipeline schemas and read the transformed value from validatorOutputs.'
evidence:
- packages/form-core/tests/validation.test.ts
- packages/form-core/tests/validation.test-d.ts
- name: on_submit_reads_value_in_schema_mode
symptom: 'onSubmit reads value even though strictSchema or looseSchema is being used.'
correction: 'Read the relevant schemaOutputs entry, because both schema modes preserve raw form value separately from parsed output.'
correction: 'Read the relevant validatorOutputs entry, because both schema modes preserve raw form value separately from parsed output.'
evidence:
- packages/form-core/tests/validation.test.ts
- packages/form-core/src/FormApi/FormApi.public.ts
Expand All @@ -551,7 +551,7 @@ skill_candidates:
purpose: 'Help users turn Standard Schema-compatible schemas into form validators, field errors, typed submit output, and realistic examples.'
entrypoints:
- 'validators'
- 'schemaOutputs'
- 'FormSubmitContext.validatorOutputs'
- 'parseIssues'
- 'createValidationError'
- 'Standard Schema validators'
Expand All @@ -566,13 +566,13 @@ skill_candidates:
teaches:
- 'Use Standard Schema-compatible validators in form or field validators.'
- 'Extract reusable validator trigger configs with createValidator or createValidators when the policy is complex enough to deserve a semantic name.'
- 'Read parsed schema output from onSubmit context.schemaOutputs.'
- 'Read parsed schema output from onSubmit context.validatorOutputs.'
- 'Return validation errors as values through createValidationError or parseIssues; do not throw them.'
- 'Let Standard Schema issue paths route field errors instead of hand-building brittle path maps.'
failure_modes:
- name: assuming_schema_output_replaces_value
symptom: 'onSubmit reads value expecting transformed/coerced data.'
correction: 'Use schemaOutputs for parsed schema output; value remains form state.'
correction: 'Use validatorOutputs for parsed schema output; value remains form state.'
evidence:
- packages/form-core/src/FormApi/FormApi.public.ts
- packages/form-core/tests/validation.test.ts
Expand Down
8 changes: 4 additions & 4 deletions _artifacts/skill_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Core guidance:
- Use `looseSchema` when the schema is a ruleset for confirming valid output, but editable defaults need nullable or undefined values, such as a date field that starts as `null`.
- In `looseSchema`, default values must still align with the schema shape, but properties may also be `null` or `undefined` where editing requires it.
- In both schema modes, callbacks may not have useful inference unless there is a schema in the validator array for the helper to infer from.
- In both schema modes, `onSubmit` should read the parsed transform output from `schemaOutputs` rather than raw `value`.
- In both schema modes, `onSubmit` should read the parsed transform output from `validatorOutputs` rather than raw `value`.
- Treat pipeline-vs-ruleset as TanStack Form guidance, not a Standard Schema concept. Standard Schema only gives the shared validation/typing interface.
- Explain the mental model as: is this schema a pipeline of constraints/transforms, or a firewall that only lets valid values through?
- List current Standard Schema-capable libraries as examples with a general leaning, not as hard categories.
Expand All @@ -190,7 +190,7 @@ Important failure modes:
- Using `looseSchema` but providing default values that no longer match the schema shape at all.
- Using schema modes without putting a schema in the validator array, then expecting callbacks to infer from that schema.
- Using loose schema mode for a true pipeline and then reading raw `value` as if it were parsed output.
- Reading `value` in `onSubmit` for schema-mode forms instead of reading the corresponding `schemaOutputs` entry.
- Reading `value` in `onSubmit` for schema-mode forms instead of reading the corresponding `validatorOutputs` entry.
- Treating all Zod `input` and `output` differences as a real pipeline decision.

Source trail:
Expand All @@ -214,15 +214,15 @@ Purpose: Help users take a Standard Schema-compatible schema and wire it into va
Primary APIs:

- `validators`
- `schemaOutputs`
- `FormSubmitContext.validatorOutputs`
- `parseIssues`
- `createValidationError`

Core guidance:

- Configure schema validation through validators.
- Extract reusable validator trigger configs with `createValidator` or `createValidators` when the behavior is complex enough to need a semantic name.
- Read parsed schema output from `schemaOutputs`; raw `value` remains form state.
- Read parsed schema output from `onSubmit` context's `validatorOutputs`; raw `value` remains form state.
- Route submit validation errors as returned values through `createValidationError` or `parseIssues`; do not throw them.
- Let Standard Schema issue paths route nested and array field errors when possible.
- Keep UI metadata explicit or schema-library-specific; Standard Schema does not standardize it.
Expand Down
2 changes: 1 addition & 1 deletion _artifacts/skill_tree.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ skills:
domain: schema-driven-forms
path: packages/react-form/skills/schema-driven-forms/SKILL.md
package: packages/react-form
description: 'Use when wiring Standard Schema-compatible validators, validators arrays, schemaOutputs, parseIssues, createValidationError, nested issue paths, and schema submit output into React forms.'
description: 'Use when wiring Standard Schema-compatible validators, validators arrays, outputs, parseIssues, createValidationError, nested issue paths, and schema submit output into React forms.'
requires:
- choosing-form-options-modes
sources:
Expand Down
2 changes: 1 addition & 1 deletion examples/react/next-server-actions-zod/src/app/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default async function someAction(_prev: unknown, formData: FormData) {
return result.serverState
}

console.log('validatedData', result.values, result.schemaOutputs)
console.log('validatedData', result.values, result.validatorOutputs)
// Persist the form data to the database
// await sql`
// INSERT INTO users (name, email, password)
Expand Down
2 changes: 1 addition & 1 deletion examples/react/next-server-actions/src/app/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default async function someAction(_prev: unknown, formData: FormData) {
return result.serverState
}

console.log('validatedData', result.values, result.schemaOutputs)
console.log('validatedData', result.values, result.validatorOutputs)
// Persist the form data to the database
// await sql`
// INSERT INTO users (name, email, password)
Expand Down
2 changes: 1 addition & 1 deletion examples/react/nextjs/app/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default async function someAction(_prev: unknown, formData: FormData) {
return result.serverState
}

console.log('validatedData', result.values, result.schemaOutputs)
console.log('validatedData', result.values, result.validatorOutputs)

return initialServerFormState
}
2 changes: 1 addition & 1 deletion examples/react/tanstack-start/src/utils/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const handleForm = createServerFn({ method: 'POST' })

console.info('validated form value', {
values: result.values,
schemaOutputs: result.schemaOutputs,
validatorOutputs: result.validatorOutputs,
})

throw redirect({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function BookingForm() {
useStepper(0, 6)
const form = useAppForm({
...bookingFormOptions,
onSubmit: async ({ schemaOutputs: [result] }) => {
onSubmit: async ({ validatorOutputs: [result] }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
alert('Form was submitted! See logs for result')
console.log(result)
Expand Down
5 changes: 3 additions & 2 deletions packages/angular-form/src/form-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
DeepKeys,
DeepValue,
FormGroupOptions,
FormGroupSubmitFn,
FormGroupValidators,
FormValidators,
ToFormErrorTypes,
Expand All @@ -35,13 +36,13 @@ export class TanStackFormGroup<
validators = input<NoInfer<TGroupValidators>>()
onSubmit =
input<
FormGroupOptions<
FormGroupSubmitFn<
TFormData,
TGroupName,
TGroupValue,
TGroupValidators,
ToFormErrorTypes<TFormValidators, TSubmitReturn>
>['onSubmit']
>
>()
onSubmitInvalid =
input<
Expand Down
33 changes: 16 additions & 17 deletions packages/form-core/src/FormApi/FormApi.public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ import type {
FormValidators,
ParsedStandardSchemaIssues,
ToFormErrorTypes,
ToFormSchemaOutputs,
ToFormOutputs,
} from '../validation.public'
import type { StandardSchemaV1Issue } from '../standardSchema.public'
import type { FormListeners } from '../listeners.public'
import type { FormApiArrayMethods } from './FormApiArrayMethods.types.public'
import type { FormApiFieldMethods } from './FormApiFieldMethods.types.public'
import type { ServerFormState } from '../ssr.public'

declare const onSubmitErrorBrand: unique symbol
import type { SUBMIT_ERROR } from './handleSubmit.lib'

/**
* A validation error marked for processing as an `onSubmit` failure.
* A validation error wrapped for processing as an `onSubmit` failure.
*
* Create this value with the `createValidationError` or `parseIssues` helper
* provided to `onSubmit`, then return it from the callback so its form- and
Expand All @@ -43,18 +42,17 @@ declare const onSubmitErrorBrand: unique symbol
*/
export type OnSubmitError<
TFormValidationError extends FormValidationError<any>,
> = TFormValidationError & {
/** Internal brand used to identify submit errors. Do not access directly. */
[onSubmitErrorBrand]: true
> = {
readonly [SUBMIT_ERROR]: TFormValidationError
}

/**
* Marks a validation error for processing as an `onSubmit` failure.
* Wraps a validation error for processing as an `onSubmit` failure without mutating it.
*
* Return the result from `onSubmit` to add its form- and field-level errors to
* validation state.
*
* @param error - The form- or field-level validation error to mark.
* @param error - The form- or field-level validation error to wrap.
* @typeParam TFormData - Library-managed. Do not specify explicitly.
* @typeParam TError - Library-managed. Do not specify explicitly.
*/
Expand Down Expand Up @@ -95,34 +93,35 @@ export type ParseSubmitIssuesFn<in out TFormData> = (
* ```
*
* @typeParam TFormData - Library-managed. Do not specify explicitly.
* @typeParam TSchemaOutputs - Library-managed. Do not specify explicitly.
* @typeParam TOutputs - Library-managed. Do not specify explicitly.
* @typeParam TFormErrorTypes - Library-managed. Do not specify explicitly.
*/
export interface FormSubmitContext<
in out TFormData,
out TSchemaOutputs,
out TOutputs,
in out TFormErrorTypes extends FormErrorTypes,
> {
/** The form values for this submission. */
value: TFormData
/** The form API handling this submission. */
formApi: FormApi<TFormData, TFormErrorTypes>
/**
* The submit outputs produced by the form's schema validators, ordered by
* validator index.
* Schema outputs and `createOutput` payloads from this submission, ordered by
* validator index. Skipped validators and successful returns without an output
* contribute `undefined`.
*
* @example
* ```ts
* {
* // ...
* onSubmit: async ({ schemaOutputs }) => {
* const validatedUser = schemaOutputs[0]
* onSubmit: async ({ validatorOutputs }) => {
* const validatedUser = validatorOutputs[0]
* await saveUser(validatedUser)
* },
* }
* ```
*/
schemaOutputs: TSchemaOutputs
validatorOutputs: TOutputs
/**
* Creates a validation error that can be returned from `onSubmit`.
*
Expand Down Expand Up @@ -188,7 +187,7 @@ export type FormSubmitFn<
> = (
context: FormSubmitContext<
TFormData,
ToFormSchemaOutputs<TFormValidators>,
ToFormOutputs<TFormValidators>,
ToFormErrorTypes<TFormValidators, unknown>
>,
) => TSubmitReturn
Expand Down
Loading
Loading