Skip to content

Add useFormData hook for full-form value reactivity in apps#87

Draft
Dan503 wants to merge 5 commits into
open-circle:mainfrom
Dan503:feature/use-form-data
Draft

Add useFormData hook for full-form value reactivity in apps#87
Dan503 wants to merge 5 commits into
open-circle:mainfrom
Dan503:feature/use-form-data

Conversation

@Dan503
Copy link
Copy Markdown

@Dan503 Dan503 commented May 7, 2026

Publishing as a Draft PR as I haven't had time to fully test it yet.

Summary

Adds a new useFormData hook (or composable / primitive / rune) across all six framework packages. It wraps the existing getInput(form) method in each framework's reactive primitive so the full form's input is exposed as a live, schema-shaped object — useful for JSON previews, debug panels, and conditional rendering based on multiple fields.

const formData = useFormData(form);
return <pre>{JSON.stringify(formData, null, 2)}</pre>;
// updates on every keystroke, in any field

Each framework's return type matches its existing useField(...).input convention so users don't have to learn a new pattern:

Framework API name Return type Read as
React useFormData PartialValues<v.InferInput<T>> formData.name
Preact useFormData ReadonlySignal<PartialValues<…>> formData.value.name
Qwik useFormData$ ReadonlySignal<PartialValues<…>> formData.value.name
Vue useFormData ComputedRef<PartialValues<…>> formData.value.name (script) / {{ formData.name }} (template)
Solid useFormData Accessor<PartialValues<…>> formData().name
Svelte useFormData FormDataStore { current } formData.current.name

What's in this PR

  • Implementation — new file in each framework's hooks/composables/primitives/runes folder (~3-line wrapper over getInput + the framework's reactive primitive). Plus a folder-level barrel and an entry added to each framework's existing barrel.
  • Docs — 6 new useFormData (or useFormData$) MDX pages + properties.ts files under website/src/routes/(docs)/. Adds a FormDataStore type page for Svelte. Each framework's API menu now lists the new entry.
  • Playground — Solid and React login playgrounds get a FormDataPreview component that renders JSON.stringify(formData, null, 2) underneath the form for end-to-end reactivity verification.
  • Version bumps (minor — new public API):
    • @formisch/preact 0.9.5 → 0.10.0
    • @formisch/qwik 0.10.0 → 0.11.0
    • @formisch/react 0.4.5 → 0.5.0
    • @formisch/solid 0.9.5 → 0.10.0
    • @formisch/svelte 0.7.5 → 0.8.0
    • @formisch/vue 0.7.5 → 0.8.0

Note on the bundled setListener fix

This branch has the React core build fix from #(setListener PR number) merged in, because without it the React playground can't build and the new hook can't be exercised end-to-end. Affects:

  • packages/core/src/framework/index.ts — adds Listener type and setListener stub
  • packages/core 0.6.3 → 0.6.4

If the standalone setListener PR lands first, this branch will need a rebase and the Change @formisch/core to v0.6.4 bullet under @formisch/react v0.5.0 should drop (since the dep change would belong to the v0.4.6 entry that already shipped). Otherwise the two changes ship together as part of the v0.5.0 react release.

Design rationale

  • No core changes for the feature itselfgetInput(form) already walks the signal tree via getFieldInput(form[INTERNAL]). When invoked inside a framework's reactive scope, signal reads auto-subscribe and the computation re-runs on any change. The new hook is just frameworkPrimitive(() => getInput(form)).
  • Per-framework return shape, not a uniform shape — matching useField(...).input's convention keeps API surface consistent within each framework.
  • Svelte uses a getter-backed object because $derived can't be returned bare from a .svelte.ts function and remain reactive at the call site. The FormDataStore { readonly current } shape matches the existing pattern used by createForm in the same package.

Test plan

  • pnpm -C frameworks/{preact,qwik,react,solid,svelte,vue} build — all six build clean.
  • pnpm -C frameworks/{preact,qwik,react,solid,svelte,vue} lint — all six lint clean.
  • Solid playground: pnpm -C playgrounds/solid devhttp://localhost:3000/login. Type "hello" into the email field. The JSON preview below the form must update on each of the 5 keystrokes (h → he → hel → hell → hello), not just on blur or after the first keystroke. Repeat for password.
  • React playground: pnpm -C playgrounds/react dev → same reactivity check.
  • (Optional) Repeat for Preact, Qwik, Vue, Svelte playgrounds — useFormData test scaffolding only added to Solid + React so far; happy to add to the rest if you want spot-checks.
  • pnpm -C website build — new MDX pages and updated menus render without errors. (Note: the website build's SSG step has 30 pre-existing Qwik lexical-scope errors in website/src/routes/playground/*/index.tsx that are unrelated to this PR — the Vite build itself succeeds.)

Summary by cubic

Adds useFormData across React, Preact, Qwik, Vue, Solid, and Svelte to expose the full form input as a live, schema-shaped value for previews and multi-field logic. Includes a small core export fix required for React builds.

  • New Features

    • New useFormData API (useFormData$ in Qwik) that wraps getInput(form) in each framework’s reactive primitive and updates on every keystroke.
    • Return shape matches each framework’s useField(...).input convention (signals/computed/accessor/value as appropriate).
    • Docs added for all frameworks; Svelte adds a FormDataStore type page.
    • React and Solid playgrounds show a JSON preview using useFormData for end-to-end checks.
  • Dependencies

    • Minor bumps for new public API: @formisch/react 0.5.0, @formisch/preact 0.10.0, @formisch/qwik 0.11.0, @formisch/solid 0.10.0, @formisch/svelte 0.8.0, @formisch/vue 0.8.0.
    • @formisch/core to 0.6.4: adds Listener type and setListener stub so React builds succeed.

Written for commit 3a0b673. Summary will update on new commits.

Daniel Tonon and others added 5 commits May 1, 2026 14:31
Adds a new `hook/composable/primitive/rune` across all six framework packages that returns a reactive snapshot of the entire form input. Wraps the existing `getInput(form)` in each framework's reactive primitive so a JSON preview of the form data updates on every keystroke. Useful for live previews, debug panels, and conditional rendering based on multiple fields at once.
Each framework returns the type that matches its existing `useField(...).input` convention:
- React: `PartialValues<v.InferInput<TSchema>>` (plain value, hook re-runs on change via useSignals)
- Preact: `ReadonlySignal<PartialValues<...>>` (useComputed)
- Qwik: `ReadonlySignal<PartialValues<...>>` (`useComputed$`, named `useFormData$`)
- Vue: `ComputedRef<PartialValues<...>>` (computed, accepts `MaybeRefOrGetter`)
- Solid: `Accessor<PartialValues<...>>` (`createMemo`, accepts `MaybeGetter`)
- Svelte: `FormDataStore { readonly current: PartialValues<...> }` (getter-backed `$derived`)
Adds documentation pages and menu entries for each framework, plus a new `FormDataStore` type doc page for Svelte. Updates the Solid and React login playgrounds with a JSON preview component that exercises the hook end-to-end.
Minor bump per package (new public API, semver). Prepends a CHANGELOG entry to each package describing the new hook.

- @formisch/react: 0.4.5 → 0.5.0
- @formisch/preact: 0.9.5 → 0.10.0
- @formisch/qwik: 0.10.0 → 0.11.0 (useFormData$)
- @formisch/solid: 0.9.5 → 0.10.0
- @formisch/svelte: 0.7.5 → 0.8.0
- @formisch/vue: 0.7.5 → 0.8.0
The framework-agnostic packages/core/src/framework/index.ts didn't declare Listener or setListener, but packages/core/src/framework/index.react.ts did. The build's `export *` re-export forwards only names visible on the base module's analysis, so setListener was silently dropped from the React core dist. This broke @formisch/react's useSignals hook (which imports setListener from @formisch/core/react) and any downstream React framework build.

Adds Listener and setListener stub declarations to the base index.ts, matching the existing pattern of "No framework selected" stubs for createId, createSignal, batch, and untrack. The React framework now picks up the real implementation from index.react.ts; the other five frameworks pick up the throwing stub but never call it, so the stub is harmless.

Audit of the other framework-specific files (packages/core/src/framework/index.{preact,qwik,solid,svelte,vue}.ts, packages/core/src/types/*.{fw}.ts, packages/methods/src/**/*.{fw}.ts) found no other missing-export bugs — every other framework variant either redeclares the same names as its base or explicitly re-exports them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Patch release for the React core build fix in the previous commit.

- @formisch/core: 0.6.3 → 0.6.4 — adds Listener type and setListener stub to the framework-agnostic framework/index.ts so the bundler includes setListener in the React core dist.
- @formisch/react: 0.4.5 → 0.4.6 — pulls in the fixed @formisch/core; the React framework's build (frameworks/react/src/hooks/useSignals/useSignals.ts) was previously failing with "MISSING_EXPORT setListener".

The other five framework packages (preact, qwik, solid, svelte, vue) are not bumped here — their builds were not affected by the bug, since none of them import setListener. If you'd prefer to bump them too to follow the existing "bump-frameworks-with-deps" convention used in the v0.4.5 release, happy to do that as a follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brings in the React core build fix (Listener + setListener stubs in packages/core/src/framework/index.ts) so the React playground can build and exercise the new useFormData hook.

Conflict resolution:
- frameworks/react/package.json: kept 0.5.0 from HEAD; the setListener fix's 0.4.6 patch bump is naturally superseded by the useFormData minor bump (semver minor includes patch fixes).
- frameworks/react/CHANGELOG.md: rolled the setListener-related core dependency change into the existing v0.5.0 entry as a second bullet, dropped the standalone v0.4.6 entry since the two changes ship together as v0.5.0.

The setListener fix will still be sent upstream as its own PR. If that PR lands first, this branch will need a rebase and the consolidated v0.5.0 changelog entry can drop the second bullet (since the fix would already be in main as v0.4.6 by then).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

Someone is attempting to deploy a commit to the Open Circle Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d5c7f48e-1327-46c5-930a-574da9eb8d5d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

❤️ Share

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

@MichaHuhn
Copy link
Copy Markdown

Thank you for this PR!
That's really useful.
I always needed to create such a getInput() wrapper for each form.

@fabian-hiller
Copy link
Copy Markdown
Member

Thank you for working on this! I'm not sure if the library should expose such a hook. This is because using such a hook is usually considered poor practice as it forces your component to re-render on every keystroke. It is generally better to only listen to specific inputs. What do you think?

@MichaHuhn
Copy link
Copy Markdown

MichaHuhn commented May 9, 2026

In my case I need to derive data from all form values in realtime.
That works perfectly in Vue but I don't know if that's an issue in React.

Without Formisch I would still use a similar approach:

const form = ref({
  exampleA: '',
  exampleB: '',
  exampleC: ''
});

// Use `derivedExample` in script or display in template.
const derivedExample = computed(() => `${form.value.exampleA}, ${form.value.exampleB}, ${form.value.exampleC}`);
// When using Formisch, I always use `getInput()` because I need all form values in realtime anyway.
const formValues = computed(() => getInput(form));
const derivedExample = computed(() => `${formValues.value.exampleA}, ${formValues.value.exampleB}, ${formValues.value.exampleC}`);

@fabian-hiller
Copy link
Copy Markdown
Member

Make sense. For now, I recommend using getInput. But if more devs requests this feature for good reasons I may reevaluate it.

// @__NO_SIDE_EFFECTS__
export function useFormData(form: FormStore): unknown {
useSignals();
return getInput(form);
Copy link
Copy Markdown
Author

@Dan503 Dan503 May 9, 2026

Choose a reason for hiding this comment

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

The issue with using getInput in React is that it is not reactive to changes and useSignals is not exposed to the user at the moment.

Exposing useSignals will at least give React users a work around.

@Dan503
Copy link
Copy Markdown
Author

Dan503 commented May 10, 2026

Thank you for working on this! I'm not sure if the library should expose such a hook. This is because using such a hook is usually considered poor practice as it forces your component to re-render on every keystroke. It is generally better to only listen to specific inputs. What do you think?

I get that this can lead to performance issues if used inappropriately however there are legitimate use cases for needing to be reactive to the entire form such as showing live debug info in a UI.

Maybe you would be willing to implement something like a useFullyReactiveFormDataButBeCarefulBecauseItCanBeReallyReallyBadForPerformanceIfUsedInappropriately(form) custom hook if you want to have people be able to use it but not automatically reach for it all the time out of laziness.

Basically taking the same strategy as how React does dangerouslySetInnerHtml. The framework makes it possible but makes it ugly and inconvenient and surfaces the main concern in the API itself so developers are less likely to use it carelessly as the default.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants