Skip to content

Preact integration#2148

Open
crutchcorn wants to merge 16 commits intomainfrom
preact-integration
Open

Preact integration#2148
crutchcorn wants to merge 16 commits intomainfrom
preact-integration

Conversation

@crutchcorn
Copy link
Copy Markdown
Member

@crutchcorn crutchcorn commented May 6, 2026

Replaces PR #2043

Summary by CodeRabbit

  • New Features

    • Added Preact framework support for building forms with a new Preact form package, quick start guide, and comprehensive documentation.
    • Added guides covering validation, composition, UI library integration, and debugging for Preact forms.
  • Documentation

    • Updated Angular form guides with improved examples and formatting.
    • Added new Preact example application.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

Adds comprehensive Preact framework support to TanStack Form, including a new @tanstack/preact-form package with hooks (useForm, useField, useFieldGroup) and utilities (createFormHook), complete documentation guides covering validation, arrays, composition, and API references, a working example application, and minor Angular documentation updates.

Changes

Preact Framework Integration

Layer / File(s) Summary
Type Definitions & Core Interfaces
packages/preact-form/src/types.ts, packages/preact-form/src/useForm.tsx, packages/preact-form/src/useField.tsx
Defines UseFieldOptions, UseFieldOptionsBound, ServerFormState types and ReactFormApi, ReactFormExtendedApi interfaces that extend core form types with Preact-specific subscribers and field components.
Core Hooks
packages/preact-form/src/useForm.tsx, packages/preact-form/src/useField.tsx, packages/preact-form/src/useFieldGroup.tsx, packages/preact-form/src/useStore.ts, packages/preact-form/src/useFormId.ts, packages/preact-form/src/useIsomorphicLayoutEffect.ts
Implements form management hooks: useForm wraps core FormApi with reactive subscriptions; useField creates managed field instances with reactive state; useFieldGroup enables field grouping; useStore bridges form subscriptions; useIsomorphicLayoutEffect provides SSR-safe effects; useFormId wraps useId.
Form Composition Factory
packages/preact-form/src/createFormHook.tsx
Implements createFormHook and createFormHookContexts to enable custom form hooks with context-based field/form access, withForm and withFieldGroup HOCs, and pre-bound component patterns. Exports AppFieldExtendedReactFormApi, WithFormProps, WithFieldGroupProps.
Package Exports & Configuration
packages/preact-form/src/index.ts, packages/preact-form/package.json, packages/preact-form/tsconfig.json, packages/preact-form/eslint.config.js
Consolidates public API exports from core and Preact hooks; configures package metadata, TypeScript compilation for JSX/Preact, and ESLint rules for React patterns.
Preact Build & Testing Setup
packages/preact-form/vite.config.ts, packages/preact-form/tsconfig.docs.json, packages/preact-form/tests/test-setup.ts, packages/preact-form/tests/utils.ts
Vite config with Preact plugin, Vitest settings, and Istanbul coverage; test setup for Preact testing library; utility helpers (sleep function).
Comprehensive Test Suite
packages/preact-form/tests/useForm.test.tsx, packages/preact-form/tests/useField.test.tsx, packages/preact-form/tests/createFormHook.test.tsx, packages/preact-form/tests/useForm.test-d.tsx, packages/preact-form/tests/useField.test-d.tsx, packages/preact-form/tests/createFormHook.test-d.tsx
Runtime and type-level tests covering form initialization, field management, validation, submission, array operations, composition patterns, and type inference.
Documentation: Guides
docs/framework/preact/guides/basic-concepts.md, docs/framework/preact/guides/validation.md, docs/framework/preact/guides/dynamic-validation.md, docs/framework/preact/guides/arrays.md, docs/framework/preact/guides/async-initial-values.md, docs/framework/preact/guides/linked-fields.md, docs/framework/preact/guides/reactivity.md, docs/framework/preact/guides/listeners.md, docs/framework/preact/guides/custom-errors.md, docs/framework/preact/guides/submission-handling.md, docs/framework/preact/guides/ui-libraries.md, docs/framework/preact/guides/focus-management.md, docs/framework/preact/guides/form-composition.md, docs/framework/preact/guides/debugging.md
Comprehensive tutorial guides covering form fundamentals, validation strategies (sync, async, schema-based), array field handling, state management, composition patterns, integration with UI libraries (Mantine, Material UI, shadcn/ui, Chakra UI), and debugging.
Documentation: API Reference
docs/framework/preact/reference/index.md, docs/framework/preact/reference/functions/*.md, docs/framework/preact/reference/interfaces/*.md, docs/framework/preact/reference/type-aliases/*.md, docs/framework/preact/reference/variables/*.md
Full API reference documentation for useForm, useField, useFieldGroup, createFormHook, createFormHookContexts, useStore, and related types/interfaces.
Quick Start Guide
docs/framework/preact/quick-start.md
Interactive quick-start guide demonstrating two usage patterns: custom form hooks with context, and minimal inline pattern with form.Field.
Example Application
examples/preact/simple/src/index.tsx, examples/preact/simple/package.json, examples/preact/simple/tsconfig.json, examples/preact/simple/vite.config.ts, examples/preact/simple/index.html, examples/preact/simple/README.md
Runnable Preact example application demonstrating form initialization, field management, validation, and submission handling.
Monorepo & Documentation Integration
docs/config.json, pnpm-workspace.yaml, scripts/generate-docs.ts, packages/preact-form/README.md
Updates docs config navigation to include Preact guides and API reference; registers Preact example in workspace; configures doc generation for Preact package; adds marketing content to package README.
Angular Documentation Refinements
docs/framework/angular/guides/arrays.md, docs/framework/angular/guides/basic-concepts.md, docs/framework/angular/guides/dynamic-validation.md, docs/framework/angular/guides/validation.md, docs/framework/angular/guides/submission-handling.md, docs/framework/angular/guides/form-composition.md, docs/framework/angular/guides/listeners.md, docs/framework/angular/quick-start.md
Minor formatting updates (trailing commas, template reformatting, multi-line imports) and additions to Angular examples (new helper methods, submission metadata handling, expanded validation patterns).

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 Preact hops in with forms so bright,
Hooks and context, validation tight,
Arrays dance and state's sublime,
Docs and guides, examples in time!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch preact-integration

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 6, 2026

View your CI Pipeline Execution ↗ for commit eb3c756

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 1m 26s View ↗
nx run-many --target=build --exclude=examples/** ✅ Succeeded 37s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-06 09:22:05 UTC

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

🚀 Changeset Version Preview

No changeset entries found. Merging this PR will not cause a version bump for any packages.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 6, 2026

More templates

@tanstack/angular-form

npm i https://pkg.pr.new/TanStack/form/@tanstack/angular-form@2148

@tanstack/form-core

npm i https://pkg.pr.new/TanStack/form/@tanstack/form-core@2148

@tanstack/form-devtools

npm i https://pkg.pr.new/TanStack/form/@tanstack/form-devtools@2148

@tanstack/lit-form

npm i https://pkg.pr.new/TanStack/form/@tanstack/lit-form@2148

@tanstack/preact-form

npm i https://pkg.pr.new/TanStack/form/@tanstack/preact-form@2148

@tanstack/react-form

npm i https://pkg.pr.new/TanStack/form/@tanstack/react-form@2148

@tanstack/react-form-devtools

npm i https://pkg.pr.new/TanStack/form/@tanstack/react-form-devtools@2148

@tanstack/react-form-nextjs

npm i https://pkg.pr.new/TanStack/form/@tanstack/react-form-nextjs@2148

@tanstack/react-form-remix

npm i https://pkg.pr.new/TanStack/form/@tanstack/react-form-remix@2148

@tanstack/react-form-start

npm i https://pkg.pr.new/TanStack/form/@tanstack/react-form-start@2148

@tanstack/solid-form

npm i https://pkg.pr.new/TanStack/form/@tanstack/solid-form@2148

@tanstack/solid-form-devtools

npm i https://pkg.pr.new/TanStack/form/@tanstack/solid-form-devtools@2148

@tanstack/svelte-form

npm i https://pkg.pr.new/TanStack/form/@tanstack/svelte-form@2148

@tanstack/vue-form

npm i https://pkg.pr.new/TanStack/form/@tanstack/vue-form@2148

commit: eb3c756

@sentry
Copy link
Copy Markdown

sentry Bot commented May 6, 2026

Codecov Report

❌ Patch coverage is 95.42484% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.61%. Comparing base (6892ed0) to head (eb3c756).
⚠️ Report is 171 commits behind head on main.

Files with missing lines Patch % Lines
packages/preact-form/src/useForm.tsx 86.48% 4 Missing and 1 partial ⚠️
packages/preact-form/src/createFormHook.tsx 97.50% 1 Missing ⚠️
packages/preact-form/src/useStore.ts 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2148      +/-   ##
==========================================
+ Coverage   90.35%   90.61%   +0.26%     
==========================================
  Files          38       56      +18     
  Lines        1752     2196     +444     
  Branches      444      550     +106     
==========================================
+ Hits         1583     1990     +407     
- Misses        149      185      +36     
- Partials       20       21       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@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: 14

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/framework/preact/reference/interfaces/ReactFormApi.md (1)

6-107: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update Preact documentation to reference Preact sources and use correct type definitions

The Preact reference documentation is incorrectly pointing to React sources and types:

  1. Source file links (lines 8, 70, 82) point to packages/react-form/src/useForm.tsx instead of packages/preact-form/src/useForm.tsx
  2. Type references show ReactNode | Promise<ReactNode> instead of the actual Preact equivalent ComponentChild
  3. Field description (line 72) says "A React component" instead of "A Preact component"
  4. children prop type (line 98) shows ReactNode | (state) => ReactNode instead of ComponentChild | (state) => ComponentChild

While both packages define an interface named ReactFormApi, the Preact implementation correctly uses Preact-specific types (ComponentChild from preact) in its actual source at packages/preact-form/src/useForm.tsx:91. The documentation must reflect this.

🤖 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 `@docs/framework/preact/reference/interfaces/ReactFormApi.md` around lines 6 -
107, The Preact docs incorrectly reference React sources and types: update the
source links for the ReactFormApi/Field/Subscribe entries to point to
packages/preact-form/src/useForm.tsx (matching the Preact implementation),
change all occurrences of React-specific types to Preact types (replace
ReactNode | Promise<ReactNode> and ReactNode | (state) => ReactNode with
ComponentChild | Promise<ComponentChild> and ComponentChild | (state) =>
ComponentChild respectively), and update the Field description text from "A
React component" to "A Preact component"; target the ReactFormApi interface and
its Field and Subscribe property docs to make these replacements so they match
the Preact source at useForm.tsx.
docs/config.json (1)

634-790: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing Preact entry in the Examples section.

The PR introduces a examples/preact/simple example (referenced in the AI summary and commit messages), but docs/config.json has no Preact entry under the "Examples" section, so it won't be reachable via the docs sidebar.

📝 Proposed addition (insert between the `react` and `vue` example blocks)
         ]
       },
+      {
+        "label": "preact",
+        "children": [
+          {
+            "label": "Simple",
+            "to": "framework/preact/examples/simple"
+          }
+        ]
+      },
       {
         "label": "vue",
🤖 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 `@docs/config.json` around lines 634 - 790, Add a missing "preact" framework
entry to the "frameworks" array in docs/config.json (insert it between the
existing "react" and "vue" objects); create an object with "label": "preact" and
a "children" array containing at least the "Simple" example pointing to
"framework/preact/examples/simple" so the new examples/preact/simple page
appears in the docs sidebar.
🟡 Minor comments (26)
docs/framework/preact/guides/reactivity.md-33-48 (1)

33-48: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Three bugs in the form.Subscribe code example

  1. form.Field is missing the required name prop (line 37) — omitting it is a TypeScript error and produces an unnamed field at runtime.
  2. field.state.lastName is incorrect (line 41) — the field API exposes the current value at field.state.value, not as a named property on the state object; this evaluates to undefined.
  3. onInput={field.handleChange} passes the DOM InputEvent (line 42) — handleChange expects the typed value, not the event. The event needs to be unwrapped.

A secondary observation: the firstName parameter captured from the selector on line 35 is never referenced inside the children callback, which makes the subscription example unclear for readers.

📝 Suggested fix
 <form.Subscribe
   selector={(state) => state.values.firstName}
-  children={(firstName) => (
-    <form.Field>
+  children={(firstName) => (
+    <form.Field name="lastName">
       {(field) => (
         <input
           name="lastName"
-          value={field.state.lastName}
-          onInput={field.handleChange}
+          value={field.state.value}
+          onInput={(e) =>
+            field.handleChange((e.target as HTMLInputElement).value)
+          }
         />
       )}
     </form.Field>
   )}
 />
🤖 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 `@docs/framework/preact/guides/reactivity.md` around lines 33 - 48, The example
in form.Subscribe has three issues: provide the required name prop to form.Field
(add name="lastName" on the form.Field invocation), read the field value from
field.state.value instead of field.state.lastName, and unwrap the DOM event
before calling field.handleChange (e.g., pass the input's value to
field.handleChange rather than the raw InputEvent). Also either use the selector
result firstName inside the children callback (e.g., display or derive a value)
or remove the unused firstName parameter to avoid confusion. Ensure these
changes are applied in the form.Subscribe / selector / children block and the
inner form.Field render (referencing form.Subscribe, form.Field,
field.state.value, and field.handleChange).
docs/framework/preact/reference/variables/Field.md-9-16 (1)

9-16: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

JSDoc refers to "React component" instead of Preact, and fix the TPatentSubmitMeta typo in the source

The Field component is defined directly in packages/preact-form/src/useField.tsx (not re-exported from react-form). However, the JSDoc at lines 682-684 incorrectly states "returns a React component." It should specify Preact. Additionally, the type parameter TPatentSubmitMeta (lines 168, 305, 319, 384, 409, 472, 744) is a typo and should be TParentSubmitMeta. Fixing the JSDoc in the source will correct the auto-generated documentation.

🤖 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 `@docs/framework/preact/reference/variables/Field.md` around lines 9 - 16,
Update the JSDoc for the Field component to say "returns a Preact component"
(replace "React" with "Preact") in the useField.tsx source and correct the type
parameter typo by renaming TPatentSubmitMeta to TParentSubmitMeta wherever it
appears (e.g., in the generic list for Field and at each occurrence you noted:
lines near the type parameter declarations and usages inside useField.tsx).
Ensure all occurrences of the misspelled generic (TPatentSubmitMeta) are changed
to TParentSubmitMeta so signatures and references compile and the generated docs
show the correct name.
docs/framework/preact/reference/variables/Field.md-108-116 (1)

108-116: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

TPatentSubmitMeta is a typo — should be TParentSubmitMeta

The generic type parameter TPatentSubmitMeta across both packages/preact-form/src/useField.tsx and packages/react-form/src/useField.tsx should be TParentSubmitMeta to align with the naming convention used for other parameters (e.g., TParentData, TFormOn*, TOn*). This typo exists in the published public API and impacts 34 occurrences across both packages. A straightforward search-and-replace fix is needed.

🤖 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 `@docs/framework/preact/reference/variables/Field.md` around lines 108 - 116,
The public API contains a typo: the generic type parameter `TPatentSubmitMeta`
should be renamed to `TParentSubmitMeta` across usages; update the generic
parameter name in all declarations and references (e.g., the
`FieldComponentProps<... , TPatentSubmitMeta>` occurrences) in both form
packages so the signature aligns with `TParentData` and other
`TParent*`/`TFormOn*` generics; ensure exports, type aliases and any
JSDoc/markdown docs (like the `TPatentSubmitMeta` entry in Field.md) are changed
accordingly and run type checks to catch remaining references.
packages/preact-form/README.md-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add alt="" to the Scarf tracking pixel.

The <img> at Line 1 has no alt attribute, triggering an MD045 lint warning. Tracking pixels conventionally use alt="".

📝 Proposed fix
-<img src="https://static.scarf.sh/a.png?x-pxid=be2d8a11-9712-4c1d-9963-580b2d4fb133" />
+<img src="https://static.scarf.sh/a.png?x-pxid=be2d8a11-9712-4c1d-9963-580b2d4fb133" alt="" />
🤖 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 `@packages/preact-form/README.md` at line 1, The README contains a tracking
pixel <img> element without an alt attribute causing an MD045 lint warning;
update the <img
src="https://static.scarf.sh/a.png?x-pxid=be2d8a11-9712-4c1d-9963-580b2d4fb133"
/> tag in README.md to include alt="" (an empty alt) so the tracking pixel is
accessible and the MD045 lint error is resolved.
packages/preact-form/README.md-21-21 (1)

21-21: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Broken URL encoding in the Best of JS badge URL.

The fullName query parameter contains TanStack%form which is not valid percent-encoding. The %f is being interpreted as the start of a hex escape sequence but %fo is not a valid hex pair. It should almost certainly be TanStack%2Fform (URL-encoded /) or TanStack/form.

📝 Proposed fix
-<a href="https://bestofjs.org/projects/tanstack-form"><img alt="Best of JS" src="https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=TanStack%form%26since=daily" /></a>
+<a href="https://bestofjs.org/projects/tanstack-form"><img alt="Best of JS" src="https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=TanStack%2Fform%26since=daily" /></a>
🤖 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 `@packages/preact-form/README.md` at line 21, The Best of JS badge URL in
README.md has an invalid percent-encoding in the img src query parameter
`fullName=TanStack%form`; replace that value with a correctly encoded project
name (either `TanStack%2Fform` or the unencoded `TanStack/form`) to fix the
broken URL. Locate the img tag (the Best of JS badge) and update the `src` query
string so `fullName` is properly encoded, then verify the badge loads correctly.
docs/framework/preact/reference/variables/useIsomorphicLayoutEffect.md-37-39 (1)

37-39: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Version 16.8.0 is a React milestone — not meaningful for Preact.

React 16.8.0 was when React Hooks were introduced, but that version number has no significance in the Preact release timeline. Consider removing this section or replacing it with the relevant Preact version that introduced hooks support.

🤖 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 `@docs/framework/preact/reference/variables/useIsomorphicLayoutEffect.md`
around lines 37 - 39, The "Version 16.8.0" line is incorrect for Preact; remove
or replace it with the correct Preact version (or delete the whole Version
section) in the docs file for useIsomorphicLayoutEffect.md so the documentation
doesn't reference the React release milestone; update the Version heading or
remove the heading entirely to reflect the actual Preact release that introduced
hooks (or omit the version if unknown).
docs/framework/preact/guides/debugging.md-12-14 (1)

12-14: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language specifiers to fenced code blocks (MD040).

Both fenced blocks contain plain-text console output. Specifying text (or sh for the tsc output) resolves the markdownlint MD040 warnings and improves syntax-highlighting behavior in rendered docs.

📝 Proposed fix
-```
+```text
 Warning: A component is changing an uncontrolled input...

...

- +text
Type instantiation is excessively deep and possibly infinite

</details>


Also applies to: 35-37

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/framework/preact/guides/debugging.md around lines 12 - 14, The fenced
code blocks that contain plain-text console output (the block starting with
"Warning: A component is changing an uncontrolled input..." and the block
starting with "Type instantiation is excessively deep and possibly infinite")
need explicit language specifiers to satisfy MD040; update those fences from totext (or use ```sh for the tsc output block) so the markdownlint warning
is resolved and syntax highlighting is correct.


</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/type-aliases/ServerFormState.md-12-12 (1)</summary><blockquote>

`12-12`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Wrong package in "Defined in" link — points to `react-form` instead of `preact-form`.**

The source reference links to `packages/react-form/src/types.ts` but this is a Preact reference page; the actual definition should be in `packages/preact-form/src/types.ts`.



<details>
<summary>📝 Proposed fix</summary>

```diff
-Defined in: [packages/react-form/src/types.ts:120](https://github.com/TanStack/form/blob/main/packages/react-form/src/types.ts#L120)
+Defined in: [packages/preact-form/src/types.ts:120](https://github.com/TanStack/form/blob/main/packages/preact-form/src/types.ts#L120)
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/type-aliases/ServerFormState.md` at line 12,
Update the "Defined in" reference in ServerFormState.md so it points to the
preact-form package's types file instead of react-form: change the link target
from packages/react-form/src/types.ts:120 to
packages/preact-form/src/types.ts:120 and ensure the displayed path text
reflects "packages/preact-form/src/types.ts" so the ServerFormState reference
correctly links to the definition in types.ts for the preact-form codebase.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/guides/async-initial-values.md-21-21 (1)</summary><blockquote>

`21-21`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Use `@tanstack/preact-query`, not `@tanstack/react-query`, in a Preact guide.**

`@tanstack/react-query` requires `preact/compat` to work in a Preact project. The Preact-native adapter is `@tanstack/preact-query` and is the correct import for Preact-targeted documentation.



<details>
<summary>📝 Proposed fix</summary>

```diff
-import { useQuery } from '@tanstack/react-query'
+import { useQuery } from '@tanstack/preact-query'
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/guides/async-initial-values.md` at line 21, The guide
imports useQuery from the React adapter; update the import so Preact uses the
correct adapter by changing the import source for useQuery from
"@tanstack/react-query" to "@tanstack/preact-query" (i.e., locate the line with
"import { useQuery }" and replace the module specifier accordingly).
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/variables/useIsomorphicLayoutEffect.md-12-12 (1)</summary><blockquote>

`12-12`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Wrong package in "Defined in" link — points to `react-form` instead of `preact-form`.**

The source reference links to `packages/react-form/src/useIsomorphicLayoutEffect.ts` but the actual Preact implementation lives at `packages/preact-form/src/useIsomorphicLayoutEffect.ts`.



<details>
<summary>📝 Proposed fix</summary>

```diff
-Defined in: [packages/react-form/src/useIsomorphicLayoutEffect.ts:3](https://github.com/TanStack/form/blob/main/packages/react-form/src/useIsomorphicLayoutEffect.ts#L3)
+Defined in: [packages/preact-form/src/useIsomorphicLayoutEffect.ts:3](https://github.com/TanStack/form/blob/main/packages/preact-form/src/useIsomorphicLayoutEffect.ts#L3)
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/variables/useIsomorphicLayoutEffect.md` at
line 12, Update the "Defined in" source link in the useIsomorphicLayoutEffect
documentation: replace the incorrect react-form reference with the Preact
implementation so the link points to the preact package's
useIsomorphicLayoutEffect source
(packages/preact-form/src/useIsomorphicLayoutEffect.ts); edit the
docs/framework/preact/reference/variables/useIsomorphicLayoutEffect.md entry
that contains the "Defined in:" link to use the correct target URL.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/guides/ui-libraries.md-51-61 (1)</summary><blockquote>

`51-61`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Use `value` (not `defaultValue`) for controlled integration with TanStack Form.**

`defaultValue` makes the input uncontrolled, so programmatic updates (e.g., `form.reset()`, `form.setFieldValue()`) won't be reflected in the visible input. The shadcn/ui and Chakra UI examples in this same file correctly use `value={state.value}`. The Mantine and Material UI examples should be consistent.

The same applies to the MUI `TextField` at Line 102.



<details>
<summary>📝 Proposed fix for the Mantine TextInput</summary>

```diff
-              defaultValue={state.value}
-              onInput={(e) => handleChange(e.target.value)}
+              value={state.value}
+              onInput={(e) => handleChange(e.target.value)}
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/guides/ui-libraries.md` around lines 51 - 61, The
Mantine and MUI examples use defaultValue which makes the inputs uncontrolled;
change them to controlled inputs by replacing defaultValue={state.value} with
value={state.value} and keep the existing onInput/onChange handlers (e.g., the
Field render prop using ({ state, handleChange, handleBlur }) should pass
value={state.value} to TextInput and the MUI TextField should also use
value={state.value}); this ensures programmatic updates like form.reset() and
form.setFieldValue() reflect in the visible input while preserving
handleChange/handleBlur behavior.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/guides/form-composition.md-692-692 (1)</summary><blockquote>

`692-692`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Add alt text for the composability chart image.**

The current image markdown is missing alt text, which hurts accessibility and lint compliance.

 

<details>
<summary>Suggested fix</summary>

```diff
-![](https://raw.githubusercontent.com/TanStack/form/main/docs/assets/react_form_composability.svg)
+![TanStack Form composability decision chart](https://raw.githubusercontent.com/TanStack/form/main/docs/assets/react_form_composability.svg)
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/guides/form-composition.md` at line 692, The image
Markdown line is missing alt text which breaks accessibility; update the
Markdown image tag in form-composition.md (the line containing the
raw.githubusercontent.com/TanStack/form/main/docs/assets/react_form_composability.svg)
to include a concise, descriptive alt attribute (e.g., "React form composability
diagram showing nested form and field interactions") so the chart is accessible
and lint-compliant.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/guides/focus-management.md-133-134 (1)</summary><blockquote>

`133-134`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Use a string for `TextInput.value` in the React Native example.**

`field.state.value` is numeric here, but React Native's `TextInput` component expects the `value` prop to be a string. Converting before binding prevents a broken copy-paste example.

<details>
<summary>Suggested fix</summary>

```diff
-              value={field.state.value}
+              value={String(field.state.value ?? '')}
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/guides/focus-management.md` around lines 133 - 134, The
React Native example binds a numeric value to TextInput.value
(field.state.value) which expects a string; convert the value to a string before
passing it to the TextInput component (e.g., use String(...) or .toString()) so
the example works for copy-paste. Update the TextInput value binding referencing
field.state.value to pass a stringified value and preserve existing behavior for
empty/undefined values as needed.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/functions/useStore.md-17-18 (1)</summary><blockquote>

`17-18`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Regenerate this page from the wrapper implementation, not the upstream `@tanstack/preact-store` package.**

The reference currently documents the raw overloads from `@tanstack/preact-store@0.8.1` instead of the wrapper in `packages/preact-form/src/useStore.ts`. This misdirection confuses consumers about both the source of the API and the actual accepted store types. The wrapper accepts `StoreLike<TState>` (a generic interface), not the upstream `Store` and `Derived` overloads shown in lines 31-41 and 68-80.

Also applies to: 31-41, 47-80, 56 (duplicate `## Call Signature` headings at lines 8 and 47)

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/functions/useStore.md` around lines 17 - 18,
The docs page was generated from the upstream `@tanstack/preact-store` types
instead of our wrapper; regenerate the reference for useStore from our wrapper
implementation (packages/preact-form/src/useStore.ts) so it reflects the
wrapper's accepted type StoreLike<TState> and its actual call signature, not the
upstream Store/Derived overloads; remove the incorrect overload blocks (the ones
matching lines 31–41 and 68–80 in the generated output) and eliminate the
duplicate "## Call Signature" heading so the page documents the wrapper's
useStore function and StoreLike<TState> interface accurately.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/type-aliases/LensFieldComponent.md-19-34 (1)</summary><blockquote>

`19-34`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Rename the second `Type Parameters` heading.**

This page defines `## Type Parameters` twice, which trips `MD024` and creates duplicate anchors. Rename the second section to something distinct, e.g. `## Call Signature Type Parameters`.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/type-aliases/LensFieldComponent.md` around
lines 19 - 34, The file repeats the "## Type Parameters" heading causing
duplicate anchors; rename the second occurrence to a distinct heading such as
"## Call Signature Type Parameters" to avoid MD024 errors and duplicate anchors;
update the section that currently lists TLensData, TParentSubmitMeta, and
ExtendedApi (the second "Type Parameters" block) so its heading is changed and
consistent with surrounding documentation.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/functions/createFormHook.md-12-12 (1)</summary><blockquote>

`12-12`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Fix the generated `Defined in` link for the Preact docs.**

This page lives under `framework/preact`, but the source reference still points at `packages/react-form/...`, so it sends readers to the wrong package. Please correct the docs generator/source mapping here and on the sibling Preact reference pages.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/functions/createFormHook.md` at line 12, The
"Defined in" source link for the Preact reference page createFormHook.md is
pointing to packages/react-form; update the docs generator or source-mapping so
that the createFormHook reference (and sibling Preact reference pages) resolve
to the correct Preact package source (e.g., the Preact implementation file for
createFormHook, not packages/react-form/src/createFormHook.tsx). Locate uses of
the mapping that produce the "Defined in" link for createFormHook.md (and
related framework/preact pages) and change the target to the Preact package
source path so the generated link points to the correct repository file for
createFormHook.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/functions/useForm.md-12-14 (1)</summary><blockquote>

`12-14`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Wrong source path and "React Hook" wording in Preact docs.**

- Line 12: `Defined in: packages/react-form/src/useForm.tsx:159` — should point to `packages/preact-form/src/useForm.tsx` (same systemic issue called out on `useFieldGroup.md`).
- Line 14: `A custom React Hook that returns an extended instance of the FormApi class.` — for the Preact reference docs this should read `Preact hook` (or simply "custom hook") to avoid suggesting a React-specific implementation.




<details>
<summary>🛠️ Proposed fix</summary>

```diff
-Defined in: [packages/react-form/src/useForm.tsx:159](https://github.com/TanStack/form/blob/main/packages/react-form/src/useForm.tsx#L159)
+Defined in: [packages/preact-form/src/useForm.tsx:159](https://github.com/TanStack/form/blob/main/packages/preact-form/src/useForm.tsx#L159)

-A custom React Hook that returns an extended instance of the `FormApi` class.
+A custom Preact hook that returns an extended instance of the `FormApi` class.
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/functions/useForm.md` around lines 12 - 14,
The docs page for useForm.md incorrectly points to the React source and uses
"React Hook" wording; update the "Defined in:" reference to point to
packages/preact-form/src/useForm.tsx (same line number if applicable) and change
the description sentence to refer to a "Preact hook" or "custom hook" that
returns an extended instance of the FormApi class; apply the same fix pattern
used for useFieldGroup.md to ensure consistency for symbols useForm and FormApi.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/functions/useFieldGroup.md-12-12 (1)</summary><blockquote>

`12-12`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Source links point to `packages/react-form` instead of `packages/preact-form` across all Preact reference documentation.**

The "Defined in" links throughout the Preact reference docs (useFieldGroup.md, useField.md, useForm.md, FieldComponent.md, etc.) incorrectly reference `packages/react-form/src/` files. The actual implementations are in `packages/preact-form/src/`.

Examples:
- `useFieldGroup.md` line 12: points to `react-form/src/useFieldGroup.tsx#L96`, should be `preact-form/src/useFieldGroup.tsx#L90`
- `useField.md` line 12: points to `react-form/src/useField.tsx#L139`, should be `preact-form/src/useField.tsx#L137`
- `useForm.md` line 12: points to `react-form/src/useForm.tsx#L159`, should be `preact-form/src/useForm.tsx#L158`

This affects all preact-form entries. The doc generator needs to be configured to use the preact-form package sources.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/functions/useFieldGroup.md` at line 12, The
Preact reference docs link to the wrong source package; update the doc
generation/config so all "Defined in" links for Preact docs point to
packages/preact-form/src (not packages/react-form/src). Specifically change the
source mapping used for the Preact templates so symbols like useFieldGroup
(useFieldGroup.md), useField (useField.md), useForm (useForm.md) and
FieldComponent (FieldComponent.md) resolve to
packages/preact-form/src/<corresponding file>.tsx (adjust the line offsets as
noted, e.g. useFieldGroup -> L90, useField -> L137, useForm -> L158) and
regenerate the Preact docs.
```

</details>

</blockquote></details>
<details>
<summary>packages/preact-form/tests/createFormHook.test-d.tsx-167-170 (1)</summary><blockquote>

`167-170`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Missing invocation of `toEqualTypeOf<...>()`**

The `toEqualTypeOf` call is missing trailing `()`, so this assertion is dropped (the expression evaluates to a method reference instead of executing the type assertion). Compare to the analogous block in `useForm.test-d.tsx` line 123-126 which closes with `}>()`.




<details>
<summary>🛠️ Proposed fix</summary>

```diff
         expectTypeOf(form.handleSubmit).toEqualTypeOf<{
           (): Promise<void>
           (submitMeta: { test: string }): Promise<void>
-        }>
+        }>()
         return <form.Test />
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/preact-form/tests/createFormHook.test-d.tsx` around lines 167 - 170,
The type assertion for form.handleSubmit is currently a reference to
toEqualTypeOf instead of being invoked; fix it by calling the matcher—append the
missing trailing "()" to the toEqualTypeOf invocation on the form.handleSubmit
assertion so the type check actually runs (i.e. change the expression using
form.handleSubmit and toEqualTypeOf to invoke toEqualTypeOf()).
```

</details>

</blockquote></details>
<details>
<summary>packages/preact-form/tests/createFormHook.test-d.tsx-303-313 (1)</summary><blockquote>

`303-313`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**`...defaultValues` spread is almost certainly a typo for `defaultValues,`**

`defaultValues` is a `Person` object, so spreading it places `firstName`/`lastName` at the top level of the `withFieldGroup` config and leaves `defaultValues` undefined. The test name says "infer subset values and props when calling withFieldGroup" but `FormGroupComponentWithProps` no longer carries `defaultValues`, so the props inference is the only thing actually exercised. This appears inconsistent with the surrounding test (`FormGroupComponent` directly above passes `defaultValues,`).




<details>
<summary>🛠️ Proposed fix</summary>

```diff
     const FormGroupComponentWithProps = withFieldGroup({
-      ...defaultValues,
+      defaultValues,
       props: {} as ComponentProps,
       render: ({ group, children, ...props }) => {
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/preact-form/tests/createFormHook.test-d.tsx` around lines 303 - 313,
The test mistakenly spreads the Person defaultValues into the withFieldGroup
config (using ...defaultValues) which drops the defaultValues property and
mis-tests inference; update the withFieldGroup call for
FormGroupComponentWithProps to pass defaultValues as a named property (use
defaultValues, not ...defaultValues) so the component config includes the
defaultValues field and the test exercises subset values and props inference
correctly for withFieldGroup.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/guides/validation.md-543-543 (1)</summary><blockquote>

`543-543`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Accessibility guidance and example conflict on submit button disabling.**

Line 543 recommends `aria-disabled`, but Line 555 demonstrates `disabled`. Please align the recommendation and snippet to avoid mixed guidance.

 


Also applies to: 555-555

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/guides/validation.md` at line 543, The guidance and
example conflict: the text recommends using aria-disabled but the code snippet
uses disabled; update both to be consistent—choose one approach (preferably
accessibility-friendly aria-disabled) and modify the explanatory sentence
referencing form.Subscribe and canSubmit plus the submit button example (the
snippet that binds canSubmit to the button) so it uses aria-disabled with
appropriate truthy/falsy binding instead of disabled; ensure the wording also
warns about keyboard focusability if using aria-disabled.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/guides/listeners.md-71-71 (1)</summary><blockquote>

`71-71`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Heading level jumps from H1 to H3.**

Line 71 should be an `##` heading to keep markdown hierarchy valid (MD001).

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/guides/listeners.md` at line 71, The "Built-in
Debouncing" heading is currently an H3 (###) which breaks the document's
markdown hierarchy; change that heading to H2 (## Built-in Debouncing) so it
follows the H1 and satisfies MD001, updating the single line that contains
"Built-in Debouncing" in docs/framework/preact/guides/listeners.md accordingly.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/interfaces/UseFieldOptions.md-8-8 (1)</summary><blockquote>

`8-8`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**“Defined in” metadata points to React source instead of Preact source.**

For this Preact interface reference, these links should target `packages/preact-form/src/types.ts` to keep source-of-truth navigation accurate.

 


Also applies to: 118-118

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/interfaces/UseFieldOptions.md` at line 8,
Update the "Defined in" metadata links for the Preact interface reference
UseFieldOptions to point to the Preact source file
(packages/preact-form/src/types.ts) instead of the React source; locate the
occurrences in docs/framework/preact/reference/interfaces/UseFieldOptions.md
(including the second occurrence noted around line 118) and change the URL and
file path fragment so the link references packages/preact-form/src/types.ts and
the correct line anchor in that file.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/interfaces/UseFieldOptionsBound.md-8-8 (1)</summary><blockquote>

`8-8`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Definition links appear to point to the wrong package.**

Both references point to `packages/react-form/src/types.ts` in a Preact API page. Please switch these to `packages/preact-form/src/types.ts` so the “Defined in” metadata matches the documented framework.

 


Also applies to: 72-72

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/interfaces/UseFieldOptionsBound.md` at line
8, Update the "Defined in" metadata links in
docs/framework/preact/reference/interfaces/UseFieldOptionsBound.md to point to
the preact package instead of react: replace occurrences of
"packages/react-form/src/types.ts" with "packages/preact-form/src/types.ts"
(this includes the main "Defined in" line and the other instance noted). Ensure
any GitHub link fragments (e.g., L81) remain the same if the symbol line numbers
match in preact; otherwise remove or correct the line anchor so the link
resolves to the preact package's types.ts reference.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/type-aliases/UseField.md-68-68 (1)</summary><blockquote>

`68-68`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Duplicate heading text (`## Type Parameters`) should be disambiguated.**

Line 68 repeats the same heading used earlier, which triggers MD024 and weakens anchor clarity.

 

<details>
<summary>Suggested fix</summary>

```diff
-## Type Parameters
+## Function Type Parameters
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/type-aliases/UseField.md` at line 68, The
second duplicate heading "## Type Parameters" in the UseField.md docs creates an
MD024 anchor conflict; rename the repeated heading (e.g., "## Type Parameters
(UseField)" or "## Type Parameters — UseField") so it is unique and update any
internal links that point to the old anchor; locate the heading by searching for
the "## Type Parameters" text within the UseField reference and modify it to a
disambiguated form.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/reference/type-aliases/UseField.md-12-12 (1)</summary><blockquote>

`12-12`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_

**Source link targets the React package in a Preact reference page.**

Line 12 currently points to `packages/react-form/...`; this should reference the Preact package source to avoid sending readers to the wrong implementation.

 

<details>
<summary>Suggested fix</summary>

```diff
-Defined in: [packages/react-form/src/useField.tsx:59](https://github.com/TanStack/form/blob/main/packages/react-form/src/useField.tsx#L59)
+Defined in: [packages/preact-form/src/useField.tsx:59](https://github.com/TanStack/form/blob/main/packages/preact-form/src/useField.tsx#L59)
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/reference/type-aliases/UseField.md` at line 12, Update
the source link in docs/framework/preact/reference/type-aliases/UseField.md so
it points to the Preact implementation of useField instead of the React package;
replace the existing URL that references packages/react-form/src/useField.tsx
with the corresponding packages/preact-form/src/useField.tsx (or the correct
preact package path containing useField.tsx) so the "Defined in" link targets
the Preact source for the UseField type alias.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>packages/preact-form/src/useStore.ts (1)</summary><blockquote>

`18-31`: _⚡ Quick win_

**Memoize the bridged store before passing it to `usePreactStore`.**

`bridgeStore(store)` creates a new object on every render. If `@tanstack/preact-store` tracks subscriptions by store reference, this will resubscribe on each update instead of keeping one stable subscription.   

<details>
<summary>♻️ Proposed fix</summary>

```diff
 import { useStore as usePreactStore } from '@tanstack/preact-store'
+import { useMemo } from 'preact/hooks'
@@
 export function useStore<TState, TSelected = NoInfer<TState>>(
   store: StoreLike<TState>,
   selector?: (state: NoInfer<TState>) => TSelected,
   options?: UseStoreOptions<TSelected>,
 ): TSelected {
+  const bridgedStore = useMemo(() => bridgeStore(store), [store])
+
   return usePreactStore(
-    bridgeStore(store) as never,
+    bridgedStore as never,
     selector as never,
     options as never,
   ) as TSelected
 }
```
</details>


Also applies to: 39-42

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/preact-form/src/useStore.ts` around lines 18 - 31, The bridged store
object returned by bridgeStore is recreated every render causing
re-subscription; instead memoize the bridged store before passing it to
usePreactStore (e.g., useMemo or a stable ref) so the same object identity is
reused across renders and only recreated when the input store changes; update
the call sites that pass bridgeStore(store) (the bridgeStore function and the
places around usePreactStore) to use the memoized/stable bridged object and do
the same for the second occurrence at lines 39-42.
```

</details>

</blockquote></details>
<details>
<summary>packages/preact-form/src/useField.tsx (1)</summary><blockquote>

`196-197`: _💤 Low value_

**Stale "React" references in JSDoc/comments inside a Preact package.**

Several comments and JSDoc strings still talk about "React Compiler", "React element", or "React component":

- Lines 196–197 and 257: "React Compiler" is React-specific; for Preact, the relevant concern is just stable `useState`/`useMemo` identities. The comment is misleading and looks copy-pasted from `react-form`.
- Line 351: `@param children A render function that takes a field API instance and returns a React element.`
- Line 682–684: `... returns a React component. The Field component uses the useField hook internally ...`

Updating these to "Preact" (or simply "component"/"VNode") would keep the public-facing TSDoc consistent with the package name.





Also applies to: 257-257, 351-352, 682-684

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/preact-form/src/useField.tsx` around lines 196 - 197, Update stale
React-specific wording in JSDoc/comments in the useField implementation: replace
phrases like "React Compiler", "React element", and "React component" with
Preact-appropriate terms such as "Preact", "component", or "VNode" where they
appear around the useField logic and the Field component docs (notably
references inside the useField function, the JSDoc `@param` children for the
render function, and the Field component description). Ensure the phrasing still
explains stability of hook identities (e.g., stable useState/useMemo identities)
and that the children param describes returning a Preact VNode or component
instead of a React element. Keep wording consistent across useField, Field, and
any nearby comments.
```

</details>

</blockquote></details>
<details>
<summary>docs/framework/preact/guides/linked-fields.md (1)</summary><blockquote>

`36-62`: _⚡ Quick win_

**Use `e.currentTarget.value` for consistency with the rest of the Preact docs/tests**

The two `onInput` handlers use `e.target.value`, while the package's tests and other Preact guides consistently use `e.currentTarget.value` (e.g., `createFormHook.test.tsx` line 23, `useForm.test.tsx` line 37). `currentTarget` is also better typed in Preact's `JSX.TargetedEvent`, so example code copied from the docs will compile cleanly under `strict` TypeScript.




<details>
<summary>🛠️ Proposed fix</summary>

```diff
-              onInput={(e) => field.handleChange(e.target.value)}
+              onInput={(e) => field.handleChange(e.currentTarget.value)}
```

Apply to both `onInput` handlers (lines 38 and 61).
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/framework/preact/guides/linked-fields.md` around lines 36 - 62, The two
input handlers in the Preact guide use e.target.value which can be poorly typed;
update both onInput handlers (the ones calling field.handleChange for the
password and confirm_password inputs) to use e.currentTarget.value instead so
examples align with the package tests (e.g., createFormHook.test.tsx) and
compile under strict TypeScript; make sure both occurrences that read the value
before calling field.handleChange are changed.
```

</details>

</blockquote></details>
<details>
<summary>packages/preact-form/tests/createFormHook.test-d.tsx (1)</summary><blockquote>

`882-897`: _💤 Low value_

**`WithFieldGroupComponent` declared outside any `it()` block**

The `it('should not allow null as prop in withForm and withFormGroup', …)` test closes at line 889; the `withFieldGroup({...})` at lines 891-896 lives directly under `describe(...)` rather than inside the test. The `// `@ts-expect-error`` for the `props: null` case will still type-check, but pairing it with the matching `withForm` assertion inside the `it` block would keep the two assertions co-located and consistent with the rest of the file.




<details>
<summary>🛠️ Proposed fix</summary>

```diff
   it('should not allow null as prop in withForm and withFormGroup', () => {
     const WithFormComponent = withForm({
       defaultValues: { name: '' },
       // `@ts-expect-error`
       props: null,
       render: () => <></>,
     })
-  })
 
-  const WithFieldGroupComponent = withFieldGroup({
-    defaultValues: { name: '' },
-    // `@ts-expect-error`
-    props: null,
-    render: () => <></>,
-  })
+    const WithFieldGroupComponent = withFieldGroup({
+      defaultValues: { name: '' },
+      // `@ts-expect-error`
+      props: null,
+      render: () => <></>,
+    })
+  })
 })
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/preact-form/tests/createFormHook.test-d.tsx` around lines 882 - 897,
The WithFieldGroupComponent declaration using withFieldGroup(...) is currently
outside the it('should not allow null as prop in withForm and withFormGroup',
...) test; move the const WithFieldGroupComponent = withFieldGroup({
defaultValues: { name: '' }, // `@ts-expect-error` props: null, render: () =>
<></>, }) into that same it() block so the two assertions (WithFormComponent and
WithFieldGroupComponent) are co-located and the // `@ts-expect-error` applies
within the intended test scope.
```

</details>

</blockquote></details>
<details>
<summary>packages/preact-form/tests/useForm.test.tsx (2)</summary><blockquote>

`952-963`: _💤 Low value_

**Misleading test error message**

The validator triggers on empty `firstName` but returns `'Last Name is required'`. Likely a copy-paste from a sibling test. Doesn't affect test correctness, but readers hunting for behaviour will be confused.




<details>
<summary>🛠️ Proposed fix</summary>

```diff
             if (value.firstName.length === 0) {
-              fields.firstName = 'Last Name is required'
+              fields.firstName = 'First Name is required'
             }
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/preact-form/tests/useForm.test.tsx` around lines 952 - 963, The
test's validator under validators.onChange checks value.firstName but sets
fields.firstName to the message 'Last Name is required' (copy/paste error);
update the message to a correct and unambiguous string such as 'First Name is
required' so fields.firstName matches the validated field (look for validators:
{ onChange: ({ value }) => { ... } } and the fields.firstName assignment).
```

</details>

---

`750-799`: _💤 Low value_

**Stray `console.log` calls in test code**

Both `onSubmit` handlers (lines 752 and 783) call `console.log(value)`. They are unused inside their test bodies and will pollute test output. Consider removing or replacing with a no-op/noted comment.




<details>
<summary>🛠️ Proposed fix</summary>

```diff
         onSubmit: async ({ value }) => {
-          // Do something with form data
-          console.log(value)
+          // No-op: onSubmit shape is the only thing under test
         },
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/preact-form/tests/useForm.test.tsx` around lines 750 - 799, Remove
the stray console.log(value) calls inside the onSubmit handlers in the test
components to avoid noisy test output: locate the two test components (Comp
functions) that call useForm and remove or replace the console.log(value) in
their onSubmit callbacks (the anonymous async onSubmit passed to useForm) with a
noop or a brief comment indicating it's intentionally empty.
```

</details>

</blockquote></details>
<details>
<summary>packages/preact-form/tests/useField.test.tsx (1)</summary><blockquote>

`1166-1209`: _⚡ Quick win_

**Restore `console.error` spy to keep test isolation.**

`vi.spyOn(console, 'error')` is never restored in this test. Please call `consoleErrorSpy.mockRestore()` (or restore all mocks in teardown) to avoid cross-test leakage.

 

<details>
<summary>Suggested fix</summary>

```diff
   it('should handle defaultValue without setstate-in-render error', async () => {
     // Spy on console.error before rendering
     const consoleErrorSpy = vi.spyOn(console, 'error')
+    try {
       // ... test body ...
-    // Should not log an error
-    expect(consoleErrorSpy).not.toHaveBeenCalled()
+      // Should not log an error
+      expect(consoleErrorSpy).not.toHaveBeenCalled()
+    } finally {
+      consoleErrorSpy.mockRestore()
+    }
   })
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/preact-form/tests/useField.test.tsx` around lines 1166 - 1209, The
test creates a spy with vi.spyOn(console, 'error') stored in consoleErrorSpy but
never restores it, leading to cross-test leakage; update the test that defines
consoleErrorSpy (inside the test rendering Comp which uses useForm and useStore)
to call consoleErrorSpy.mockRestore() after the assertions (or register a
teardown restoring it) so the console.error spy is removed and subsequent tests
are unaffected.
```

</details>

</blockquote></details>

</blockquote></details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `25960b47-8bd3-4d23-b6f7-b16432bc7a66`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 93be6f01ba07c290dbec34d6c170f79e9eb2dfef and eb3c756fbc02771418afd00a359f9d4cd0b25cfa.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`

</details>

<details>
<summary>📒 Files selected for processing (75)</summary>

* `docs/config.json`
* `docs/framework/angular/guides/arrays.md`
* `docs/framework/angular/guides/basic-concepts.md`
* `docs/framework/angular/guides/dynamic-validation.md`
* `docs/framework/angular/guides/form-composition.md`
* `docs/framework/angular/guides/listeners.md`
* `docs/framework/angular/guides/submission-handling.md`
* `docs/framework/angular/guides/validation.md`
* `docs/framework/angular/quick-start.md`
* `docs/framework/preact/guides/arrays.md`
* `docs/framework/preact/guides/async-initial-values.md`
* `docs/framework/preact/guides/basic-concepts.md`
* `docs/framework/preact/guides/custom-errors.md`
* `docs/framework/preact/guides/debugging.md`
* `docs/framework/preact/guides/dynamic-validation.md`
* `docs/framework/preact/guides/focus-management.md`
* `docs/framework/preact/guides/form-composition.md`
* `docs/framework/preact/guides/linked-fields.md`
* `docs/framework/preact/guides/listeners.md`
* `docs/framework/preact/guides/reactivity.md`
* `docs/framework/preact/guides/submission-handling.md`
* `docs/framework/preact/guides/ui-libraries.md`
* `docs/framework/preact/guides/validation.md`
* `docs/framework/preact/quick-start.md`
* `docs/framework/preact/reference/functions/createFormHook.md`
* `docs/framework/preact/reference/functions/createFormHookContexts.md`
* `docs/framework/preact/reference/functions/useField.md`
* `docs/framework/preact/reference/functions/useFieldGroup.md`
* `docs/framework/preact/reference/functions/useForm.md`
* `docs/framework/preact/reference/functions/useStore.md`
* `docs/framework/preact/reference/index.md`
* `docs/framework/preact/reference/interfaces/ReactFormApi.md`
* `docs/framework/preact/reference/interfaces/UseFieldOptions.md`
* `docs/framework/preact/reference/interfaces/UseFieldOptionsBound.md`
* `docs/framework/preact/reference/interfaces/WithFieldGroupProps.md`
* `docs/framework/preact/reference/interfaces/WithFormProps.md`
* `docs/framework/preact/reference/type-aliases/FieldComponent.md`
* `docs/framework/preact/reference/type-aliases/LensFieldComponent.md`
* `docs/framework/preact/reference/type-aliases/ReactFormExtendedApi.md`
* `docs/framework/preact/reference/type-aliases/ServerFormState.md`
* `docs/framework/preact/reference/type-aliases/UseField.md`
* `docs/framework/preact/reference/variables/Field.md`
* `docs/framework/preact/reference/variables/useIsomorphicLayoutEffect.md`
* `examples/preact/simple/README.md`
* `examples/preact/simple/index.html`
* `examples/preact/simple/package.json`
* `examples/preact/simple/src/index.tsx`
* `examples/preact/simple/tsconfig.json`
* `examples/preact/simple/vite.config.ts`
* `packages/preact-form/README.md`
* `packages/preact-form/eslint.config.js`
* `packages/preact-form/package.json`
* `packages/preact-form/src/createFormHook.tsx`
* `packages/preact-form/src/index.ts`
* `packages/preact-form/src/types.ts`
* `packages/preact-form/src/useField.tsx`
* `packages/preact-form/src/useFieldGroup.tsx`
* `packages/preact-form/src/useForm.tsx`
* `packages/preact-form/src/useFormId.ts`
* `packages/preact-form/src/useIsomorphicLayoutEffect.ts`
* `packages/preact-form/src/useStore.ts`
* `packages/preact-form/tests/createFormHook.test-d.tsx`
* `packages/preact-form/tests/createFormHook.test.tsx`
* `packages/preact-form/tests/test-setup.ts`
* `packages/preact-form/tests/useField.test-d.tsx`
* `packages/preact-form/tests/useField.test.tsx`
* `packages/preact-form/tests/useForm.test-d.tsx`
* `packages/preact-form/tests/useForm.test.tsx`
* `packages/preact-form/tests/utils.ts`
* `packages/preact-form/tsconfig.docs.json`
* `packages/preact-form/tsconfig.json`
* `packages/preact-form/vite.config.ts`
* `pnpm-workspace.yaml`
* `scripts/generate-docs.ts`
* `terminalOutput`

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* terminalOutput

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines 30 to +39
<form (submit)="handleSubmit($event)">
<button type="submit" (click)="
handleClick({ submitAction: 'continue' })
">Submit and continue</button>
<button type="submit" (click)="
handleClick({ submitAction: 'backToMenu' })
">Submit and back to menu</button>
<button type="submit" (click)="handleClick({ submitAction: 'continue' })">
Submit and continue
</button>
<button
type="submit"
(click)="handleClick({ submitAction: 'backToMenu' })"
>
Submit and back to menu
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the native submit path for the default-meta case.

handleSubmit() now only cancels the event, so pressing Enter or submitting the form without a button click never calls this.form.handleSubmit(). That makes the documented onSubmitMeta default unreachable unless a click handler fires first.

One way to keep per-button meta without breaking keyboard submit
 export class AppComponent {
+  pendingMeta: FormMeta | undefined
+
   handleSubmit(event: SubmitEvent) {
     event.preventDefault()
     event.stopPropagation()
+    this.form.handleSubmit(this.pendingMeta)
+    this.pendingMeta = undefined
   }

   handleClick(meta: FormMeta) {
-    this.form.handleSubmit(meta)
+    this.pendingMeta = meta
   }
 }

Also applies to: 56-63

🤖 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 `@docs/framework/angular/guides/submission-handling.md` around lines 30 - 39,
The form submit handler handleSubmit currently always cancels the event so
native keyboard/form submits never reach this.form.handleSubmit() and the
default onSubmitMeta path is unreachable; change handleSubmit to only
preventDefault when you need to override (e.g., when handling per-button meta)
and otherwise allow the native submit to call this.form.handleSubmit(), or
detect the button metadata from event.submitter (if present) inside handleSubmit
to set submitAction before calling this.form.handleSubmit(); update/remove the
always-cancelling behavior in handleSubmit and stop relying solely on
handleClick to call this.form.handleSubmit() so onSubmitMeta default remains
reachable.

Comment on lines +32 to +51
const form = useForm({
defaultValues: {
firstName: data?.firstName ?? '',
lastName: data?.lastName ?? '',
},
onSubmit: async ({ value }) => {
// Do something with form data
console.log(value)
},
})

if (isLoading) return <p>Loading...</p>

return (
// ...
)
}
```

This will show a loading spinner until the data is fetched, and then it will render the form with the fetched data as the initial values.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The example will always render empty form fields after data loads.

useForm initializes its internal store from defaultValues only once — on the first call. Here's the problematic sequence:

  1. First render (loading): data is undefined, so defaultValues resolves to { firstName: '', lastName: '' }. The form store is initialized with empty strings. The loading spinner is returned early.
  2. Second render (data ready): useForm is called again with { firstName: 'FirstName', ... }, but the form store is already initialized and ignores the new defaultValues.
  3. Result: the form renders with empty fields.

The idiomatic fix is to split the form into a child component so that useForm only mounts (and initializes) after the data is available:

📝 Proposed fix
-export default function App() {
-  const {data, isLoading} = useQuery({
-    queryKey: ['data'],
-    queryFn: async () => {
-      await new Promise((resolve) => setTimeout(resolve, 1000))
-      return {firstName: 'FirstName', lastName: "LastName"}
-    }
-  })
-
-  const form = useForm({
-    defaultValues: {
-      firstName: data?.firstName ?? '',
-      lastName: data?.lastName ?? '',
-    },
-    onSubmit: async ({ value }) => {
-      // Do something with form data
-      console.log(value)
-    },
-  })
-
-  if (isLoading) return <p>Loading...</p>
-
-  return (
-    // ...
-  )
-}
+function MyForm({ data }) {
+  const form = useForm({
+    defaultValues: {
+      firstName: data.firstName,
+      lastName: data.lastName,
+    },
+    onSubmit: async ({ value }) => {
+      // Do something with form data
+      console.log(value)
+    },
+  })
+
+  return (
+    // ...
+  )
+}
+
+export default function App() {
+  const { data, isLoading } = useQuery({
+    queryKey: ['data'],
+    queryFn: async () => {
+      await new Promise((resolve) => setTimeout(resolve, 1000))
+      return { firstName: 'FirstName', lastName: 'LastName' }
+    },
+  })
+
+  if (isLoading) return <p>Loading...</p>
+
+  return <MyForm data={data} />
+}
🤖 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 `@docs/framework/preact/guides/async-initial-values.md` around lines 32 - 51,
The example initializes useForm too early (when data is undefined) so
defaultValues are locked to empty strings; fix by deferring useForm until after
data is loaded: keep the isLoading check in the parent and only render a child
component (e.g., <UserForm />) once data is available, then call useForm({
defaultValues: { firstName: data.firstName, lastName: data.lastName }, onSubmit:
... }) inside that child and pass data as props if needed; this ensures
useForm's internal store initializes with the fetched values instead of empty
defaults.

Comment on lines +68 to +86
Just as you might access errors from an `onChange` or `onBlur` validation, you can access errors from the `onDynamic` validation function using the `form.state.errorMap` object.

```tsx
function App() {
const form = useForm({
// ...
validationLogic: revalidateLogic(),
validators: {
onDynamic: ({ value }) => {
if (!value.firstName) {
return { firstName: 'A first name is required' }
}
return undefined
},
},
})

return <p>{form.state.errorMap.onDynamic?.firstName}</p>
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a subscription here instead of reading form.state directly.

Lines 85 and 121-122 read form.state during render, but useForm does not subscribe the parent component for you. These examples will stay stale after validation changes unless they use form.Subscribe or useStore(form.store, ...).

🛠️ Suggested doc update
-  return <p>{form.state.errorMap.onDynamic?.firstName}</p>
+  return (
+    <form.Subscribe selector={(state) => state.errorMap.onDynamic?.firstName}>
+      {(firstNameError) => <p>{firstNameError}</p>}
+    </form.Subscribe>
+  )

Also applies to: 119-123

🤖 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 `@docs/framework/preact/guides/dynamic-validation.md` around lines 68 - 86, The
example reads form.state.errorMap directly during render (e.g., using
form.state.errorMap.onDynamic?.firstName) but useForm does not subscribe the
component so the value will go stale; update the examples (the App example and
the other occurrence around lines 119–123) to read the errorMap via a
subscription API instead — either wrap the output with form.Subscribe(...) or
use useStore(form.store, selector) to select form.state.errorMap/onDynamic so
the component subscribes to updates and re-renders when validation errors
change.

Comment on lines +161 to +164
<input
type="number"
onInput={(e) => field.handleChange(e.target.valueAsNumber)}
onBlur={field.handleBlur}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n docs/framework/preact/guides/dynamic-validation.md | sed -n '155,170p'

Repository: TanStack/form

Length of output: 696


🏁 Script executed:

cat -n examples/preact/simple/src/index.tsx

Repository: TanStack/form

Length of output: 3852


Update to use e.currentTarget.valueAsNumber for consistency with the reference example.

The documentation example at line 163 uses e.target.valueAsNumber, but the reference implementation in examples/preact/simple/src/index.tsx consistently uses e.currentTarget (lines 58, 77). This matters in Preact TSX—e.currentTarget is the typed reference to the handler's element and prevents potential type errors for developers copying this pattern.

Suggested update
-              onInput={(e) => field.handleChange(e.target.valueAsNumber)}
+              onInput={(e) =>
+                field.handleChange(e.currentTarget.valueAsNumber)
+              }
📝 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
<input
type="number"
onInput={(e) => field.handleChange(e.target.valueAsNumber)}
onBlur={field.handleBlur}
<input
type="number"
onInput={(e) =>
field.handleChange(e.currentTarget.valueAsNumber)
}
onBlur={field.handleBlur}
🤖 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 `@docs/framework/preact/guides/dynamic-validation.md` around lines 161 - 164,
The input handler uses e.target.valueAsNumber which can cause typing issues in
Preact TSX; update the onInput arrow to read the numeric value from
e.currentTarget.valueAsNumber instead so the element is the correctly typed
reference (keep the handlers field.handleChange and field.handleBlur unchanged).

Comment on lines +154 to +157
<form.AppForm>
// Notice the `AppForm` component wrapper; `AppForm` provides the required
context
<form.SubscribeButton label="Submit" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n docs/framework/preact/guides/form-composition.md | sed -n '150,165p'

Repository: TanStack/form

Length of output: 625


🏁 Script executed:

cat -n docs/framework/preact/guides/form-composition.md | sed -n '140,165p'

Repository: TanStack/form

Length of output: 849


This JSX example is syntactically invalid due to inline // comments inside returned markup.

As written, the snippet won't compile if copied. Use JSX comments ({/* ... */}) or move commentary outside the returned tree.

Suggested fix
   return (
     <form.AppForm>
-      // Notice the `AppForm` component wrapper; `AppForm` provides the required
-      context
+      {/* Notice the `AppForm` component wrapper; `AppForm` provides the required context */}
       <form.SubscribeButton label="Submit" />
     </form.AppForm>
   )
🤖 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 `@docs/framework/preact/guides/form-composition.md` around lines 154 - 157, The
JSX snippet is invalid because it uses JavaScript // comments inside returned
markup; update the example around the AppForm and SubscribeButton components so
comments are either moved outside the JSX or written as JSX comments (e.g., {/*
... */}) inside the returned tree; ensure the <form.AppForm> wrapper and the
<form.SubscribeButton label="Submit" /> line remain unchanged except for the
comment style so the snippet compiles.

Comment on lines +431 to +446
>): WithFormProps<
UnwrapOrAny<TFormData>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnMount>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnChange>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnChangeAsync>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnBlur>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnBlurAsync>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnSubmit>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnSubmitAsync>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnDynamic>,
UnwrapDefaultOrAny<
undefined | FormValidateOrFn<TFormData>,
TOnDynamicAsync
>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnServer>,
UnwrapOrAny<TSubmitMeta>,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
from pathlib import Path
import re

src = Path("packages/preact-form/src/createFormHook.tsx").read_text()
m = re.search(r"WithFormProps<(?P<body>[\s\S]*?)>\['render'\]", src)
body = m.group("body")

for name in [
    "TOnChangeAsync",
    "TOnBlurAsync",
    "TOnSubmitAsync",
    "TOnDynamicAsync",
    "TOnServer",
]:
    hit = re.search(
        rf"UnwrapDefaultOrAny<\s*undefined \| (\w+)<TFormData>,\s*{name}",
        body,
    )
    actual = hit.group(1) if hit else "missing"
    print(f"{name}: {actual}")
PY

Repository: TanStack/form

Length of output: 216


🏁 Script executed:

cd packages/preact-form/src && grep -n "TOnChangeAsync\|TOnBlurAsync\|TOnSubmitAsync\|TOnDynamicAsync\|TOnServer" createFormHook.tsx | head -30

Repository: TanStack/form

Length of output: 1271


🏁 Script executed:

cat -n packages/preact-form/src/createFormHook.tsx | grep -A 2 -B 2 "TOnChangeAsync\|TOnBlurAsync\|TOnSubmitAsync\|TOnDynamicAsync\|TOnServer" | head -50

Repository: TanStack/form

Length of output: 2195


🏁 Script executed:

cat -n packages/preact-form/src/createFormHook.tsx | sed -n '340,380p'

Repository: TanStack/form

Length of output: 1427


Use async defaults for the async validator generics.

The UnwrapDefaultOrAny fallback for TOnChangeAsync, TOnBlurAsync, TOnSubmitAsync, TOnDynamicAsync, and TOnServer uses FormValidateOrFn, but these generics are constrained to FormAsyncValidateOrFn. This type mismatch prevents the default-match check from succeeding, causing withForm() to leak the full constraint instead of unwrapping to any.

Fix the async/server fallback types
-    UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnChangeAsync>,
+    UnwrapDefaultOrAny<
+      undefined | FormAsyncValidateOrFn<TFormData>,
+      TOnChangeAsync
+    >,
     UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnBlur>,
-    UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnBlurAsync>,
+    UnwrapDefaultOrAny<
+      undefined | FormAsyncValidateOrFn<TFormData>,
+      TOnBlurAsync
+    >,
     UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnSubmit>,
-    UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnSubmitAsync>,
+    UnwrapDefaultOrAny<
+      undefined | FormAsyncValidateOrFn<TFormData>,
+      TOnSubmitAsync
+    >,
     UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnDynamic>,
     UnwrapDefaultOrAny<
-      undefined | FormValidateOrFn<TFormData>,
+      undefined | FormAsyncValidateOrFn<TFormData>,
       TOnDynamicAsync
     >,
-    UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnServer>,
+    UnwrapDefaultOrAny<
+      undefined | FormAsyncValidateOrFn<TFormData>,
+      TOnServer
+    >,
📝 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
>): WithFormProps<
UnwrapOrAny<TFormData>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnMount>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnChange>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnChangeAsync>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnBlur>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnBlurAsync>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnSubmit>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnSubmitAsync>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnDynamic>,
UnwrapDefaultOrAny<
undefined | FormValidateOrFn<TFormData>,
TOnDynamicAsync
>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnServer>,
UnwrapOrAny<TSubmitMeta>,
>): WithFormProps<
UnwrapOrAny<TFormData>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnMount>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnChange>,
UnwrapDefaultOrAny<
undefined | FormAsyncValidateOrFn<TFormData>,
TOnChangeAsync
>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnBlur>,
UnwrapDefaultOrAny<
undefined | FormAsyncValidateOrFn<TFormData>,
TOnBlurAsync
>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnSubmit>,
UnwrapDefaultOrAny<
undefined | FormAsyncValidateOrFn<TFormData>,
TOnSubmitAsync
>,
UnwrapDefaultOrAny<undefined | FormValidateOrFn<TFormData>, TOnDynamic>,
UnwrapDefaultOrAny<
undefined | FormAsyncValidateOrFn<TFormData>,
TOnDynamicAsync
>,
UnwrapDefaultOrAny<
undefined | FormAsyncValidateOrFn<TFormData>,
TOnServer
>,
UnwrapOrAny<TSubmitMeta>,
🤖 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 `@packages/preact-form/src/createFormHook.tsx` around lines 431 - 446, The
async/server validator generic fallbacks are using the sync type
FormValidateOrFn, causing the UnwrapDefaultOrAny check to fail; update the
UnwrapDefaultOrAny instantiations for TOnChangeAsync, TOnBlurAsync,
TOnSubmitAsync, TOnDynamicAsync and TOnServer inside the WithFormProps generic
list to use FormAsyncValidateOrFn instead of FormValidateOrFn so the
default-match unwrap behaves correctly (look for the UnwrapDefaultOrAny<>
entries near WithFormProps in createFormHook.tsx and replace the async-related
FormValidateOrFn usages with FormAsyncValidateOrFn).

TFormOnDynamic extends undefined | FormValidateOrFn<TParentData>,
TFormOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TParentData>,
TFormOnServer extends undefined | FormAsyncValidateOrFn<TParentData>,
TPatentSubmitMeta,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all occurrences of the typo across the repo to scope the rename
rg -nP --type=ts --type=tsx 'TPatentSubmitMeta'
rg -nP --type=md 'TPatentSubmitMeta'

Repository: TanStack/form

Length of output: 14295


🏁 Script executed:

#!/bin/bash
# Search for TPatentSubmitMeta in source code (without problematic type filter)
echo "=== Searching for TPatentSubmitMeta in source code ==="
rg 'TPatentSubmitMeta' packages/

echo ""
echo "=== Searching for TParentSubmitMeta in source code ==="
rg 'TParentSubmitMeta' packages/

echo ""
echo "=== Checking the specific file mentioned ==="
cat packages/preact-form/src/useField.tsx | head -50

Repository: TanStack/form

Length of output: 8496


🏁 Script executed:

#!/bin/bash
# Verify the specific context around line 31 in preact-form useField.tsx
echo "=== Line 31 context in preact-form useField.tsx ==="
sed -n '20,40p' packages/preact-form/src/useField.tsx

echo ""
echo "=== Count of TPatentSubmitMeta vs TParentSubmitMeta in preact-form useField.tsx ==="
echo "TPatentSubmitMeta occurrences:"
rg 'TPatentSubmitMeta' packages/preact-form/src/useField.tsx | wc -l
echo "TParentSubmitMeta occurrences:"
rg 'TParentSubmitMeta' packages/preact-form/src/useField.tsx | wc -l

echo ""
echo "=== Verify core types use correct name ==="
grep -n 'TParentSubmitMeta' packages/form-core/src/FieldApi.ts | head -5

Repository: TanStack/form

Length of output: 1396


Typo in generic parameter name: TPatentSubmitMetaTParentSubmitMeta.

The identifier TPatentSubmitMeta appears 17 times in this file and is clearly a typo of TParentSubmitMeta. The correct name is consistently used in the core types (packages/form-core/src/FieldApi.ts) and other frameworks (vue, svelte, lit). Because it leaks into the public API surface (UseField, FieldComponent, LensFieldComponent) and has propagated into the generated reference docs (e.g., docs/framework/preact/reference/functions/useField.md), it is worth fixing now before consumers depend on the misspelled name.

The typo also appears in packages/react-form/src/useField.tsx and packages/solid-form/src/createField.tsx with the same scope and impact.

🛠️ Proposed fix (file-wide rename)
-  TPatentSubmitMeta,
+  TParentSubmitMeta,

(applied at every occurrence in preact-form, react-form, and solid-form useField/createField files)

🤖 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 `@packages/preact-form/src/useField.tsx` at line 31, Rename the misspelled
generic type parameter TPatentSubmitMeta to TParentSubmitMeta throughout this
file (all 17 occurrences) so public API types match core types; update every
declaration and usage including exported types and interfaces such as UseField,
FieldComponent, LensFieldComponent and any function/type signatures that
reference TPatentSubmitMeta; apply the same rename in the analogous files
(react-form useField.tsx and solid-form createField.tsx) to keep APIs consistent
with packages/form-core/src/FieldApi.ts.

Comment on lines +18 to +29
function LocalSubscribe({
lens,
selector,
children,
}: PropsWithChildren<{
lens: AnyFieldGroupApi
selector: (state: FieldGroupState<any>) => FieldGroupState<any>
}>): ReturnType<FunctionComponent> {
const data = useStore(lens.store, selector)

return <>{functionalUpdate(children, data)}</>
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'useStore.ts' packages/preact-form/src
fd -t f 'useStore.tsx' packages/preact-form/src
rg -nP -C3 '\buseStore\b' packages/preact-form/src

Repository: TanStack/form

Length of output: 6919


🏁 Script executed:

cat -n packages/preact-form/src/useStore.ts

Repository: TanStack/form

Length of output: 1498


🏁 Script executed:

cat -n packages/preact-form/src/useFieldGroup.tsx | head -90

Repository: TanStack/form

Length of output: 3808


🏁 Script executed:

cat -n packages/preact-form/src/useFieldGroup.tsx | sed -n '210,250p'

Repository: TanStack/form

Length of output: 1361


🏁 Script executed:

cat -n packages/preact-form/src/useForm.tsx | sed -n '140,160p'

Repository: TanStack/form

Length of output: 846


LocalSubscribe selector parameter should be optional and support generic return types.

The public Subscribe API (lines 84–87) declares selector as optional and uses a generic TSelected type, allowing flexible return types. However, LocalSubscribe (line 24) marks selector as required and restricts the return type to FieldGroupState<any>.

This creates two problems:

  1. Type mismatch: Subscribe (line 228) passes props.selector, which can be undefined, directly to LocalSubscribe, which declares selector as required. TypeScript should flag this.
  2. Type inflexibility: Users cannot use custom selectors that return transformed state. The implementation should match the public API's generic signature.

Since useStore already handles optional selectors correctly (its selector parameter is optional at line 36 of useStore.ts), LocalSubscribe just needs to reflect this in its type signature.

🛠️ Proposed fix
-function LocalSubscribe({
+function LocalSubscribe<TSelected = FieldGroupState<any>>({
   lens,
   selector,
   children,
-}: PropsWithChildren<{
+}: PropsWithChildren<{
   lens: AnyFieldGroupApi
-  selector: (state: FieldGroupState<any>) => FieldGroupState<any>
+  selector?: (state: FieldGroupState<any>) => TSelected
 }>): ReturnType<FunctionComponent> {
   const data = useStore(lens.store, selector)

   return <>{functionalUpdate(children, data)}</>
 }
📝 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
function LocalSubscribe({
lens,
selector,
children,
}: PropsWithChildren<{
lens: AnyFieldGroupApi
selector: (state: FieldGroupState<any>) => FieldGroupState<any>
}>): ReturnType<FunctionComponent> {
const data = useStore(lens.store, selector)
return <>{functionalUpdate(children, data)}</>
}
function LocalSubscribe<TSelected = FieldGroupState<any>>({
lens,
selector,
children,
}: PropsWithChildren<{
lens: AnyFieldGroupApi
selector?: (state: FieldGroupState<any>) => TSelected
}>): ReturnType<FunctionComponent> {
const data = useStore(lens.store, selector)
return <>{functionalUpdate(children, data)}</>
}
🤖 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 `@packages/preact-form/src/useFieldGroup.tsx` around lines 18 - 29,
LocalSubscribe's prop types require selector and force the return to
FieldGroupState<any>, which conflicts with the public Subscribe API that allows
an optional generic selector; update LocalSubscribe's PropsWithChildren generic
so selector is optional and typed as selector?: (state: FieldGroupState<any>) =>
TSelected (or generic S) and make LocalSubscribe generic (e.g. <TSelected>) so
useStore(lens.store, selector) and functionalUpdate(children, data) preserve the
selector's flexible return type; ensure AnyFieldGroupApi, useStore, and
Subscribe signatures align with the new generic optional selector.

>
})

useIsomorphicLayoutEffect(formLensApi.mount, [formLensApi])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether FieldGroupApi has an update method matching the useForm pattern.
ast-grep --pattern $'class FieldGroupApi {
  $$$
}'
rg -nP --type=ts -C2 '\bclass\s+FieldGroupApi\b' packages/form-core/src
rg -nP --type=ts -C3 '\bupdate\s*=\s*\(' packages/form-core/src/FieldGroupApi.ts

Repository: TanStack/form

Length of output: 354


🏁 Script executed:

# Read the full FieldGroupApi class definition
head -n 250 packages/form-core/src/FieldGroupApi.ts | tail -n 150

Repository: TanStack/form

Length of output: 4040


🏁 Script executed:

# Check the end of FieldGroupApi.ts to see all methods
tail -n 100 packages/form-core/src/FieldGroupApi.ts

Repository: TanStack/form

Length of output: 2661


🏁 Script executed:

# Get line count to understand file size
wc -l packages/form-core/src/FieldGroupApi.ts

Repository: TanStack/form

Length of output: 101


🏁 Script executed:

# Search for all methods in FieldGroupApi, especially update
rg -n 'update\(' packages/form-core/src/FieldGroupApi.ts

Repository: TanStack/form

Length of output: 39


🏁 Script executed:

# Check useForm.tsx for the mount and update pattern mentioned in the review
rg -n -A 5 'useIsomorphicLayoutEffect.*mount' packages/preact-form/src/useForm.tsx

Repository: TanStack/form

Length of output: 308


🏁 Script executed:

# Get more context around the mount effect in useForm.tsx
sed -n '258,275p' packages/preact-form/src/useForm.tsx

Repository: TanStack/form

Length of output: 571


🏁 Script executed:

# Check if FormApi has an update method
rg -n 'update\s*=' packages/form-core/src/FormApi.ts | head -20

Repository: TanStack/form

Length of output: 75


🏁 Script executed:

# Check the FieldGroupApi constructor and mount method
rg -n -A 2 'mount\s*=' packages/form-core/src/FieldGroupApi.ts

Repository: TanStack/form

Length of output: 111


🏁 Script executed:

# Also check useFieldGroup.tsx to see the full context of line 256
sed -n '250,270p' packages/preact-form/src/useFieldGroup.tsx

Repository: TanStack/form

Length of output: 215


🏁 Script executed:

# Look at the full update method in FormApi to understand what it does
sed -n '1456,1480p' packages/form-core/src/FormApi.ts

Repository: TanStack/form

Length of output: 606


🏁 Script executed:

# Check FieldGroupApi constructor to see how it uses/stores opts
sed -n '200,320p' packages/form-core/src/FieldGroupApi.ts | head -80

Repository: TanStack/form

Length of output: 2169


🏁 Script executed:

# Search for mount implementation in FieldGroupApi to see if it uses opts
rg -n -B 3 -A 10 'mount.*=' packages/form-core/src/FieldGroupApi.ts

Repository: TanStack/form

Length of output: 483


🏁 Script executed:

# Check the full opts parameter type in useFieldGroup to see what can change
rg -n -B 5 'useFieldGroup\(' packages/preact-form/src/useFieldGroup.tsx | head -30

Repository: TanStack/form

Length of output: 39


🏁 Script executed:

# Get the full useFieldGroup function
cat -n packages/preact-form/src/useFieldGroup.tsx | head -n 260

Repository: TanStack/form

Length of output: 9274


🏁 Script executed:

# Search for where opts might be updated in useFieldGroup
rg -n 'opts' packages/preact-form/src/useFieldGroup.tsx | head -30

Repository: TanStack/form

Length of output: 281


🏁 Script executed:

# Check if FieldGroupApi stores opts anywhere
rg -n 'this.*opts' packages/form-core/src/FieldGroupApi.ts

Repository: TanStack/form

Length of output: 367


Add missing update effect to sync option changes with underlying form.

useFieldGroup only calls formLensApi.mount once, but unlike useForm, it lacks an update effect to re-sync opts (like defaultValues, fields, onSubmitMeta) on prop changes. The FieldGroupApi class does not expose an update method, so changes to these options after the initial mount will not be reflected. Consider syncing the options to the underlying form on each render to match the useForm pattern.

Proposed fix
  useIsomorphicLayoutEffect(formLensApi.mount, [formLensApi])
+
+  /**
+   * Sync options to the underlying form on each render. The mount effect initializes
+   * the field group, but option changes (defaultValues, fields, onSubmitMeta) need to be
+   * re-applied to keep the API in sync with the latest props.
+   */
+  useIsomorphicLayoutEffect(() => {
+    formLensApi.form.update?.(opts)
+  })
🤖 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 `@packages/preact-form/src/useFieldGroup.tsx` at line 256, useFieldGroup
currently only calls formLensApi.mount once and never re-syncs prop changes; add
an update effect to sync opts on prop changes (e.g. defaultValues, fields,
onSubmitMeta) so the underlying form stays current. Specifically, in
useFieldGroup add a useIsomorphicLayoutEffect (or extend the existing one) that
runs when formLensApi or the opts object (or the specific fields
opts.defaultValues, opts.fields, opts.onSubmitMeta) change and invokes the
formLensApi mount/sync behavior to push the latest opts into the FieldGroupApi
(since FieldGroupApi lacks an update method, call formLensApi.mount or a
dedicated sync helper on updates). Ensure the dependency array includes
formLensApi and the relevant opts pieces to mirror the useForm update pattern.

Comment on lines +625 to +781
it('should not allow withFieldGroups with different metas to be nested', () => {
type Nested = {
firstName: string
}
type Wrapper = {
field: Nested
}
type FormValues = {
form: Wrapper
unrelated: { something: { lastName: string } }
}

const defaultValues: FormValues = {
form: {
field: {
firstName: 'Test',
},
},
unrelated: {
something: {
lastName: '',
},
},
}

const LensNestedNoMeta = withFieldGroup({
defaultValues: defaultValues.form.field,
render: function Render() {
return <></>
},
})
const LensNestedWithMeta = withFieldGroup({
defaultValues: defaultValues.form.field,
onSubmitMeta: { meta: '' },
render: function Render() {
return <></>
},
})
const LensWrapper = withFieldGroup({
defaultValues: defaultValues.form,
render: function Render({ group }) {
return (
<div>
<LensNestedNoMeta form={group} fields="field" />
<LensNestedWithMeta
// @ts-expect-error Wrong meta!
form={group}
fields="field"
/>
</div>
)
},
})

it('should allow mapping withFieldGroup to different fields', () => {
const defaultValues = {
firstName: '',
lastName: '',
age: 0,
relatives: [{ firstName: '', lastName: '', age: 0 }],
}
const defaultFields = {
first: '',
last: '',
}

const form = useAppForm({
defaultValues,
})

const FieldGroup = withFieldGroup({
defaultValues: defaultFields,
render: function Render() {
return <></>
},
})

const Component1 = (
<FieldGroup
form={form}
fields={{
first: 'lastName',
last: 'firstName',
}}
/>
)

const Component2 = (
<FieldGroup
form={form}
fields={{
first: 'relatives[0].lastName',
last: 'relatives[0].firstName',
}}
/>
)
})

it('should not allow fields mapping if the top level is an array', () => {
const defaultValues = {
firstName: '',
lastName: '',
age: 0,
relatives: [{ firstName: '', lastName: '', age: 0 }],
relativesRecord: {
something: { firstName: '', lastName: '', age: 0 },
} as Record<string, { firstName: string; lastName: string }>,
}
const defaultFields = {
firstName: '',
lastName: '',
}

const form = useAppForm({
defaultValues,
})

const FieldGroupRecord = withFieldGroup({
defaultValues: { anything: defaultFields } as Record<
string,
typeof defaultFields
>,
render: function Render() {
return <></>
},
})
const FieldGroupArray = withFieldGroup({
defaultValues: [defaultFields],
render: function Render() {
return <></>
},
})

const CorrectComponent1 = (
<FieldGroupRecord form={form} fields="relativesRecord" />
)
const WrongComponent1 = (
<FieldGroupRecord
form={form}
// @ts-expect-error because record is non-indexable
fields={{
'any field goes': 'relatives[0]',
}}
/>
)
const CorrectComponent3 = (
<FieldGroupArray form={form} fields="relatives" />
)
const WrongComponent2 = (
<FieldGroupArray
form={form}
// @ts-expect-error because arrays are non-indexable
fields={{}}
/>
)
})
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the structural problem: count `it(` opens and matching closes inside the outer it block
fd 'createFormHook.test-d.tsx' --exec rg -nC1 "^\s*it\(|^\s*describe\(|^\s*\}\)" {}

Repository: TanStack/form

Length of output: 12808


Move nested it() blocks to be siblings of the outer it()

The outer it('should not allow withFieldGroups with different metas to be nested', …) at line 625 contains two nested it() calls at lines 678 and 723. Vitest does not support nesting test blocks this way and will throw at runtime (Calling the test function inside another test is not allowed). The closing }) at line 781 closes the outer it, confirming the two inner blocks are incorrectly nested.

Close the outer it after LensWrapper and move the two inner blocks to be siblings within describe('createFormHook').

Proposed fix
       },
     })
+  })
+
+  it('should allow mapping withFieldGroup to different fields', () => {
-
-    it('should allow mapping withFieldGroup to different fields', () => {
       const defaultValues = {
🤖 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 `@packages/preact-form/tests/createFormHook.test-d.tsx` around lines 625 - 781,
The outer test it('should not allow withFieldGroups with different metas to be
nested') currently contains two nested it() blocks; close that outer it
immediately after the LensWrapper declaration (the withFieldGroup call assigned
to LensWrapper) and move the two inner tests (the it blocks titled 'should allow
mapping withFieldGroup to different fields' and 'should not allow fields mapping
if the top level is an array') out so they are sibling tests under the same
describe('createFormHook') scope; ensure you keep the same test bodies and
imports and remove the extra closing brace that currently delays closing the
outer it.

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.

2 participants