Skip to content

feat(#4): TanStack Form adapter — formdraft/tanstack-form subpath#11

Merged
mayrang merged 1 commit into
mainfrom
feat/issue-4-tanstack-form-adapter
May 27, 2026
Merged

feat(#4): TanStack Form adapter — formdraft/tanstack-form subpath#11
mayrang merged 1 commit into
mainfrom
feat/issue-4-tanstack-form-adapter

Conversation

@mayrang
Copy link
Copy Markdown
Owner

@mayrang mayrang commented May 27, 2026

Closes #4.

Summary

  • New subpath formdraft/tanstack-form exports useFormDraftTanstack(form, options)
  • Mirrors the formdraft/rhf and formdraft/formik pattern: thin adapter over useFormDraft, returns the persistence-layer surface (status, lastSavedAt, error, save, discard, etc.) without the value/set/patch API (TanStack Form owns those)
  • @tanstack/react-form added as optional peer dependency
  • Bundle 4.09 KB brotli (main entry unchanged; TanStack adapter in its own chunk)

Vetting

4 rounds of code review + adversarial audit dispatched in parallel each round. Real bugs caught:

  • Type intersection any & {...} collapsed to any → explicit TanstackFormApi<T> interface
  • Schema drift on restore keys → filtered against originalDefaults
  • defaultValues: undefined from useForm → silent restore no-op (fixed with fallback to restored keys)
  • setFieldValue's dontUpdateMeta: true doesn't skip validation → added dontValidate: true so onChange validators don't paint errors on restored data
  • HIGH: <form.Field name="x" defaultValue=""> (idiomatic TanStack) silently reseeded restored values back to default on every render because dontUpdateMeta kept isTouched: false → dropped dontUpdateMeta entirely; restore now marks fields touched

Test plan

  • npm test — 115 unit tests pass (10 TanStack adapter + 105 existing)
  • npx size-limit — 4.09 KB brotli
  • Manual: integrate the README example with a TanStack form + Zod schema, verify refresh restores, discard clears, submit clears

Closes #4. Mirrors the formdraft/rhf + formdraft/formik pattern for users
on TanStack Form.

  const form = useForm({
    defaultValues: { name: '', bio: '' },
    onSubmit: async ({ value }) => {
      await api.submit(value);
      discard(); // clears storage + broadcasts + resets form
    },
  });
  const { status, lastSavedAt, discard } = useFormDraftTanstack(form, {
    key: 'profile-form',
    schema: zodAdapter(Schema),
    sync: api.saveProfile,
  });

4 rounds of code review + adversarial audit dispatched in parallel each
round. Real bugs caught and fixed before merge:

  R1 (LOW): type intersection `any & {...}` collapsed to `any` →
      explicit TanstackFormApi<T> interface with the four methods we
      actually call.
  R1 (LOW): schema-drift via Object.keys(restored) → filtered against
      originalDefaultsRef so stored keys outside the current schema
      can't be forwarded to setFieldValue.
  R2 (MEDIUM, B1): when useForm omits defaultValues, originalDefaults
      was {} → validKeys empty → restore silently no-op'd. Fixed with
      a fallback: if defaults are empty, use Object.keys(restored)
      directly (already gated by useFormDraft's schema validation).
  R2 (LOW-MEDIUM, B5): setFieldValue's `dontUpdateMeta: true` only
      suppresses meta writes — validation still runs. A form with
      onChange validators would paint errors against restored text the
      user never typed on every page load. Added `dontValidate: true`.
  R3 (HIGH, D2): `<form.Field name="x" defaultValue="">` is idiomatic
      TanStack usage. With our prior `dontUpdateMeta: true`, the field
      stayed `isTouched: false` after restore → TanStack's
      FieldApi.update re-evaluates on every render and reseeds the
      value back to `opts.defaultValue` when `!isTouched`. Restored
      data was silently wiped on every render after restore. Fixed by
      dropping `dontUpdateMeta` entirely — restore now marks fields
      as touched, blocking the reseed.
  R4: stale docs from D2 fix in both README and JSDoc still claimed
      `dontUpdateMeta` was used. Updated to describe the actual
      behavior + reason `dontUpdateMeta` was deliberately omitted.

10 unit tests:
  - persist on input change
  - restore from storage on mount
  - discard clears storage AND resets visible form
  - delete-back-to-default still persists deletion
  - restore does NOT trigger redundant sync (no flicker on page load)
  - restore survives across re-renders with form.Field defaultValue (D2)
  - restore handles nested defaultValues (subtree replacement)
  - restore does NOT trigger field-level validators (B5)
  - restore even when useForm omits defaultValues (B1)
  - user-types-before-restore race (deferred storage)

Wired in:
  - package.json: ./tanstack-form export + @tanstack/react-form optional peer
  - tsup: tanstack-form/index entry → dist/tanstack-form/{index.mjs,.js,.d.ts}
  - README adds TanStack Form integration section with submit-pattern example

Total 115 unit tests pass. Bundle 4.09 KB brotli (TanStack adapter in its
own chunk; main entry unchanged).
@mayrang mayrang force-pushed the feat/issue-4-tanstack-form-adapter branch from b508c0e to 84fc007 Compare May 27, 2026 18:09
@mayrang mayrang merged commit f0ba7c1 into main May 27, 2026
2 checks passed
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.

v0.2: TanStack Form adapter (formdraft/tanstack-form)

1 participant