Skip to content

fix(form-core): keep undefined out of the field error array type - #2383

Open
CodeAndParty wants to merge 1 commit into
TanStack:mainfrom
CodeAndParty:fix/field-meta-errors-nonnullable
Open

fix(form-core): keep undefined out of the field error array type#2383
CodeAndParty wants to merge 1 commit into
TanStack:mainfrom
CodeAndParty:fix/field-meta-errors-nonnullable

Conversation

@CodeAndParty

@CodeAndParty CodeAndParty commented Sep 11, 2026

Copy link
Copy Markdown

🎯 Changes

field.state.meta.errors carries a spurious undefined in its element type, so the array can't be iterated without a guard or a cast:

const form = useForm({
  defaultValues: { name: '' },
  validators: { onChange: z.object({ name: z.string().min(1, 'Required') }) },
})

// (StandardSchemaV1Issue | undefined)[]
field.state.meta.errors.map((e) => e.message)
//                                 ^ 'e' is possibly 'undefined'

Cause

FieldLikeMetaDerived['errors'] unions UnwrapOneLevelOfArray<UnwrapFieldValidateOrFn<…>> across all nine validator slots. A slot with no validator resolves to undefined, and UnwrapOneLevelOfArray<undefined> is undefined (T extends (infer U)[] ? U : T), so every unused slot contributes undefined to the element union. A single form-level schema leaves eight slots unused.

This is not specific to onDynamic or to Standard Schema — any config that leaves slots empty is affected.

Why NonNullable is the right fix

The value is already filtered at runtime, in FormApi:

fieldErrors = Object.values(currBaseMeta.errorMap ?? {}).filter(
  (val) => val !== undefined,
)

Group meta does the same (if (curr === undefined) return acc). And the form-level types already wrap their unions in NonNullableFormState['errors'] and getAllErrors()['form']['errors'] both do. FieldLikeMetaDerived['errors'] was the only one that didn't, which is why two adjacent tests with the identical validator disagreed:

// onChange: () => '123' as const
expectTypeOf(form.state.errors).toEqualTypeOf<Array<'123'>>()                  // form
expectTypeOf(field.state.meta.errors).toEqualTypeOf<Array<'123' | undefined>>() // field

So this aligns the field-level type with both the runtime and the existing form-level convention.

Updated assertions

Five existing type tests encoded the old shape and are updated to drop | undefined — this is the behavioural crux of the PR, so worth a close look:

  • form-core/tests/FieldApi.test-d.ts (4 assertions)
  • form-core/tests/FormGroupApi.test-d.ts, mirrored in react-form, solid-form, preact-form

Two runtime tests had defensively written key={err?.toString()}; the optional chain is now flagged as unnecessary by @typescript-eslint/no-unnecessary-condition and has been removed — a nice confirmation that the fix reaches real call sites.

Added coverage

Two regression tests in form-core/tests/standardSchemaValidator.test-d.ts:

  • form-level schema only → Array<StandardSchemaV1Issue>, and .map((i) => i.message) is Array<string>
  • form-level schema + a field validator returning 'Required' | undefinedArray<StandardSchemaV1Issue | 'Required'>, confirming a validator's own return type is still preserved and only the spurious undefined is dropped

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

Verified across all 14 packages: test:types (TS 5.4–5.9), test:lib, test:eslint, and build all pass.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Corrected form and field error array types so they contain only actual validation errors, excluding spurious undefined values.
    • Standard Schema validation errors can now be iterated and mapped without additional guards or type casts.
    • Preserved custom validator error types while improving type accuracy across React, Preact, Solid, and core form APIs.

`FieldLikeMetaDerived['errors']` unions `UnwrapOneLevelOfArray<...>` over all
nine validator slots. Every slot without a validator resolves to `undefined`,
and `UnwrapOneLevelOfArray<undefined>` is `undefined`, so each unused slot
leaked `undefined` into the element type.

With a single form-level Standard Schema — eight slots unused — the array
inferred as `(StandardSchemaV1Issue | undefined)[]`, which cannot be iterated
without a guard or a cast.

The value is already filtered at runtime, and the form-level `errors` types
already wrap their union in `NonNullable`. Wrap the field-level union to match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 6953dbd0-beee-40c2-980b-911a73a300c2

📥 Commits

Reviewing files that changed from the base of the PR and between 57a855b and e82ebbc.

📒 Files selected for processing (10)
  • .changeset/tidy-errors-narrow.md
  • packages/form-core/src/types.ts
  • packages/form-core/tests/FieldApi.test-d.ts
  • packages/form-core/tests/FormGroupApi.test-d.ts
  • packages/form-core/tests/standardSchemaValidator.test-d.ts
  • packages/preact-form/tests/useField.test.tsx
  • packages/preact-form/tests/useFormGroup.test-d.tsx
  • packages/react-form/tests/useField.test.tsx
  • packages/react-form/tests/useFormGroup.test-d.tsx
  • packages/solid-form/tests/createFormGroup.test-d.tsx

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The change narrows field and group error array element types by removing undefined from validator error unions. Type tests across form-core, React, Preact, Solid, and Standard Schema validate the updated behavior.

Changes

Error type narrowing

Layer / File(s) Summary
Field error type contract
packages/form-core/src/types.ts, .changeset/tidy-errors-narrow.md
FieldLikeMetaDerived.errors now excludes null and undefined from its element type. A patch release changeset documents the fix.
Error type validation
packages/form-core/tests/*, packages/preact-form/tests/*, packages/react-form/tests/*, packages/solid-form/tests/*
Type tests verify definite validator error types, Standard Schema issue mapping, and direct error rendering without optional chaining.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to e82eb

The change consistently narrows error-array element types without altering runtime validation behavior. No actionable merge risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 9 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description clearly explains the type fix, its cause, runtime alignment, test updates, and release impact. It includes all required template sections and completed checklist items.
Title check ✅ Passed The title is concise and accurately identifies the main change: removing the spurious undefined from the form-core field error array type.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 9 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant