Preact integration#2148
Conversation
# Conflicts: # pnpm-lock.yaml
📝 WalkthroughWalkthroughAdds comprehensive Preact framework support to TanStack Form, including a new ChangesPreact Framework Integration
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
|
View your CI Pipeline Execution ↗ for commit eb3c756
☁️ Nx Cloud last updated this comment at |
🚀 Changeset Version PreviewNo changeset entries found. Merging this PR will not cause a version bump for any packages. |
Codecov Report❌ Patch coverage is 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. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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 winUpdate Preact documentation to reference Preact sources and use correct type definitions
The Preact reference documentation is incorrectly pointing to React sources and types:
- Source file links (lines 8, 70, 82) point to
packages/react-form/src/useForm.tsxinstead ofpackages/preact-form/src/useForm.tsx- Type references show
ReactNode | Promise<ReactNode>instead of the actual Preact equivalentComponentChild- Field description (line 72) says "A React component" instead of "A Preact component"
childrenprop type (line 98) showsReactNode | (state) => ReactNodeinstead ofComponentChild | (state) => ComponentChildWhile both packages define an interface named
ReactFormApi, the Preact implementation correctly uses Preact-specific types (ComponentChildfrompreact) in its actual source atpackages/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 winMissing Preact entry in the Examples section.
The PR introduces a
examples/preact/simpleexample (referenced in the AI summary and commit messages), butdocs/config.jsonhas 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 winThree bugs in the
form.Subscribecode example
form.Fieldis missing the requirednameprop (line 37) — omitting it is a TypeScript error and produces an unnamed field at runtime.field.state.lastNameis incorrect (line 41) — the field API exposes the current value atfield.state.value, not as a named property on the state object; this evaluates toundefined.onInput={field.handleChange}passes the DOMInputEvent(line 42) —handleChangeexpects the typed value, not the event. The event needs to be unwrapped.A secondary observation: the
firstNameparameter captured from the selector on line 35 is never referenced inside thechildrencallback, 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 winJSDoc refers to "React component" instead of Preact, and fix the
TPatentSubmitMetatypo in the sourceThe
Fieldcomponent is defined directly inpackages/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 parameterTPatentSubmitMeta(lines 168, 305, 319, 384, 409, 472, 744) is a typo and should beTParentSubmitMeta. 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
TPatentSubmitMetais a typo — should beTParentSubmitMetaThe generic type parameter
TPatentSubmitMetaacross bothpackages/preact-form/src/useField.tsxandpackages/react-form/src/useField.tsxshould beTParentSubmitMetato 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 winAdd
alt=""to the Scarf tracking pixel.The
<img>at Line 1 has noaltattribute, triggering an MD045 lint warning. Tracking pixels conventionally usealt="".📝 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 winBroken URL encoding in the Best of JS badge URL.
The
fullNamequery parameter containsTanStack%formwhich is not valid percent-encoding. The%fis being interpreted as the start of a hex escape sequence but%fois not a valid hex pair. It should almost certainly beTanStack%2Fform(URL-encoded/) orTanStack/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 winVersion
16.8.0is 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 winAdd language specifiers to fenced code blocks (MD040).
Both fenced blocks contain plain-text console output. Specifying
text(orshfor thetscoutput) resolves the markdownlintMD040warnings 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.mdaround 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 fromtotext (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 - + ``` </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 -->
| <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> |
There was a problem hiding this comment.
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.
| 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. |
There was a problem hiding this comment.
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:
- First render (loading):
dataisundefined, sodefaultValuesresolves to{ firstName: '', lastName: '' }. The form store is initialized with empty strings. The loading spinner is returned early. - Second render (data ready):
useFormis called again with{ firstName: 'FirstName', ... }, but the form store is already initialized and ignores the newdefaultValues. - 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.
| 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> | ||
| } |
There was a problem hiding this comment.
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.
| <input | ||
| type="number" | ||
| onInput={(e) => field.handleChange(e.target.valueAsNumber)} | ||
| onBlur={field.handleBlur} |
There was a problem hiding this comment.
🧩 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.tsxRepository: 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.
| <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).
| <form.AppForm> | ||
| // Notice the `AppForm` component wrapper; `AppForm` provides the required | ||
| context | ||
| <form.SubscribeButton label="Submit" /> |
There was a problem hiding this comment.
🧩 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.
| >): 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>, |
There was a problem hiding this comment.
🧩 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}")
PYRepository: TanStack/form
Length of output: 216
🏁 Script executed:
cd packages/preact-form/src && grep -n "TOnChangeAsync\|TOnBlurAsync\|TOnSubmitAsync\|TOnDynamicAsync\|TOnServer" createFormHook.tsx | head -30Repository: 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 -50Repository: 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.
| >): 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, |
There was a problem hiding this comment.
🧩 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 -50Repository: 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 -5Repository: TanStack/form
Length of output: 1396
Typo in generic parameter name: TPatentSubmitMeta → TParentSubmitMeta.
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.
| 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)}</> | ||
| } |
There was a problem hiding this comment.
🧩 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/srcRepository: TanStack/form
Length of output: 6919
🏁 Script executed:
cat -n packages/preact-form/src/useStore.tsRepository: TanStack/form
Length of output: 1498
🏁 Script executed:
cat -n packages/preact-form/src/useFieldGroup.tsx | head -90Repository: 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:
- Type mismatch:
Subscribe(line 228) passesprops.selector, which can beundefined, directly toLocalSubscribe, which declaresselectoras required. TypeScript should flag this. - 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.
| 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]) |
There was a problem hiding this comment.
🧩 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.tsRepository: 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 150Repository: 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.tsRepository: TanStack/form
Length of output: 2661
🏁 Script executed:
# Get line count to understand file size
wc -l packages/form-core/src/FieldGroupApi.tsRepository: TanStack/form
Length of output: 101
🏁 Script executed:
# Search for all methods in FieldGroupApi, especially update
rg -n 'update\(' packages/form-core/src/FieldGroupApi.tsRepository: 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.tsxRepository: 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.tsxRepository: 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 -20Repository: 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.tsRepository: 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.tsxRepository: 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.tsRepository: 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 -80Repository: 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.tsRepository: 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 -30Repository: TanStack/form
Length of output: 39
🏁 Script executed:
# Get the full useFieldGroup function
cat -n packages/preact-form/src/useFieldGroup.tsx | head -n 260Repository: 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 -30Repository: TanStack/form
Length of output: 281
🏁 Script executed:
# Check if FieldGroupApi stores opts anywhere
rg -n 'this.*opts' packages/form-core/src/FieldGroupApi.tsRepository: 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.
| 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={{}} | ||
| /> | ||
| ) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
🧩 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.
Replaces PR #2043
Summary by CodeRabbit
New Features
Documentation