Skip to content

fix(Form): use FormState type to support nullable/custom initial state values#6553

Open
rdjanuar wants to merge 2 commits into
nuxt:v4from
rdjanuar:fix/missmatch-type-state-form
Open

fix(Form): use FormState type to support nullable/custom initial state values#6553
rdjanuar wants to merge 2 commits into
nuxt:v4from
rdjanuar:fix/missmatch-type-state-form

Conversation

@rdjanuar
Copy link
Copy Markdown
Contributor

@rdjanuar rdjanuar commented Jun 4, 2026

🔗 Linked issue

Resolves #5204, #6309

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📝 Description

The state strictly inferred all field types directly from the schema (based on InferInput<S>).
While this seemed ideal, it caused significant friction because HTML/DOM inputs naturally handle unvalidated string states (like binding "" to an <input type="number">). Developers were forced to either cast their reactive state to any or write overly complex schemas (e.g., z.number().or(z.string()).pipe(...)) just to satisfy TypeScript before the data was actually submitted and coerced.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@rdjanuar rdjanuar requested a review from benjamincanac as a code owner June 4, 2026 02:37
@github-actions github-actions Bot added the v4 #4488 label Jun 4, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new exported generic type FormState that maps each key of InferInput to an optional unknown. Updates src/runtime/components/Form.vue imports and the FormProps generic so the optional state prop and the validate callback parameter use FormState instead of Partial<InferInput>.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: updating Form component to use FormState type for supporting nullable/custom initial state values.
Linked Issues check ✅ Passed The PR introduces FormState type to replace Partial<InferInput>, allowing form state to accept nullable/custom initial values while preserving validation, directly addressing issue #5204.
Out of Scope Changes check ✅ Passed All changes are scoped to Form typing: updates to FormProps type signature and addition of FormState utility type, both directly related to resolving the linked issues.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description clearly explains the issue being fixed: Form state typing was too strict, causing friction because HTML inputs naturally handle unvalidated strings. The fix introduces a FormState type to support nullable and custom initial values.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/types/form.ts`:
- Around line 37-39: The FormState mapped type currently widens every field to
unknown because of the `| unknown`; replace that with a proper optional mapping
to preserve per-field types: make FormState use Partial<InferInput<S>> or `{ [K
in keyof InferInput<S>]?: InferInput<S>[K] }` (remove `| unknown`) so fields
keep their schema types; reference symbols: FormState, FormSchema, InferInput.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00db3156-0877-4c9f-9026-9f5a5ef98743

📥 Commits

Reviewing files that changed from the base of the PR and between ed2f955 and 157f92b.

📒 Files selected for processing (2)
  • src/runtime/components/Form.vue
  • src/runtime/types/form.ts

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 4, 2026

npm i https://pkg.pr.new/@nuxt/ui@6553

commit: 68789b5

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/runtime/types/form.ts (1)

37-39: ⚡ Quick win

Add JSDoc documentation explaining the type safety tradeoff.

The FormState type intentionally uses any to support runtime state values that differ from schema input types. This significant design decision should be documented with a JSDoc comment explaining:

  • Why any is used (runtime values like "" for number fields, File | null for file fields)
  • The type safety tradeoff (no compile-time checking for field values)
  • Reference to the issues it resolves (#5204, #6309)
📝 Suggested JSDoc documentation
+/**
+ * Form state type that accepts nullable, mixed, or custom initial values
+ * differing from schema input types.
+ * 
+ * Uses `any` for field values to support common runtime patterns where initial
+ * state differs from schema (e.g., `File | null` for file inputs, `number | ""`
+ * for empty numeric inputs). Schema validation occurs at runtime.
+ * 
+ * `@example`
+ * // Schema expects { age: number }, but state can initialize as:
+ * const state: FormState<MySchema> = { age: "" }  // empty input
+ * 
+ * `@see` https://github.com/nuxt/ui/issues/5204
+ * `@see` https://github.com/nuxt/ui/issues/6309
+ */
 export type FormState<S extends FormSchema> = {
   [K in keyof InferInput<S>]?: any
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/types/form.ts` around lines 37 - 39, Add a JSDoc block above the
FormState type explaining that FormState<S extends FormSchema> intentionally
uses `any` for values (e.g., string "" for numeric inputs, File | null for file
inputs) to support runtime representations that differ from schema input types,
note the tradeoff that this disables compile-time value checking for field
values, and reference issues `#5204` and `#6309`; mention related types FormSchema
and InferInput to clarify context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/types/form.ts`:
- Around line 37-39: The FormState type currently uses `any` which removes
compile-time safety; change the value type to `unknown` in the FormState
definition so it reads as {[K in keyof InferInput<S>]?: unknown} (referencing
FormState, FormSchema and InferInput) and then update any downstream usages
(e.g., Form.vue validate callbacks and custom validators) to perform explicit
type narrowing or assertions when reading fields to satisfy the new constraint.

---

Nitpick comments:
In `@src/runtime/types/form.ts`:
- Around line 37-39: Add a JSDoc block above the FormState type explaining that
FormState<S extends FormSchema> intentionally uses `any` for values (e.g.,
string "" for numeric inputs, File | null for file inputs) to support runtime
representations that differ from schema input types, note the tradeoff that this
disables compile-time value checking for field values, and reference issues
`#5204` and `#6309`; mention related types FormSchema and InferInput to clarify
context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1436db9c-402b-4627-afd7-93b917cbef87

📥 Commits

Reviewing files that changed from the base of the PR and between 0a2a8e1 and 68789b5.

📒 Files selected for processing (1)
  • src/runtime/types/form.ts

Comment thread src/runtime/types/form.ts
Comment on lines +37 to +39
export type FormState<S extends FormSchema> = {
[K in keyof InferInput<S>]?: any
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Consider unknown instead of any to preserve minimal type safety.

While any achieves the PR objective of accepting runtime values that differ from schema types (e.g., File | null, number | ""), it completely removes compile-time type checking for field values. Developers lose autocomplete, IntelliSense, and protection against typos or type errors.

Using unknown instead would still allow any runtime value but require explicit type narrowing or assertions before use, providing a safer middle ground.

🔒 Alternative using `unknown` for safer typing
 export type FormState<S extends FormSchema> = {
-  [K in keyof InferInput<S>]?: any
+  [K in keyof InferInput<S>]?: unknown
 }

Note: This change would require downstream consumers (Form.vue validate callbacks, custom validation functions) to use type assertions or guards when accessing state fields. Evaluate whether the added safety justifies the ergonomic cost.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type FormState<S extends FormSchema> = {
[K in keyof InferInput<S>]?: any
}
export type FormState<S extends FormSchema> = {
[K in keyof InferInput<S>]?: unknown
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/types/form.ts` around lines 37 - 39, The FormState type currently
uses `any` which removes compile-time safety; change the value type to `unknown`
in the FormState definition so it reads as {[K in keyof InferInput<S>]?:
unknown} (referencing FormState, FormSchema and InferInput) and then update any
downstream usages (e.g., Form.vue validate callbacks and custom validators) to
perform explicit type narrowing or assertions when reading fields to satisfy the
new constraint.

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

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UForm state type is incorrectly constrained to schema input (breaks nullable fields, other mixed-type form state)

1 participant