diff --git a/.changeset/release-4-0-4.md b/.changeset/release-4-0-4.md new file mode 100644 index 0000000..472be7b --- /dev/null +++ b/.changeset/release-4-0-4.md @@ -0,0 +1,8 @@ +--- +'hookform-action': patch +'hookform-action-core': patch +'hookform-action-standalone': patch +'hookform-action-devtools': patch +--- + +Publish a patch release with the latest docs, examples, lint/format alignment, and core adapter consistency updates. diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index fa28abc..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(pnpm tsc:*)", - "Bash(pnpm build:*)", - "Bash(pnpm dlx publint:*)", - "Bash(pnpm dlx madge:*)", - "Bash(pnpm dlx jscpd:*)", - "Bash(pnpm turbo build:*)", - "Bash(pnpm test:*)", - "Bash(pnpm add:*)", - "Bash(pnpm install:*)", - "Bash(npx biome check:*)", - "Bash(pnpm lint:*)", - "Bash(pnpm check-types)" - ] - } -} diff --git a/.gitignore b/.gitignore index 2db59c1..b0c87b6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ coverage *.tsbuildinfo .env .env.local +.claude/ diff --git a/MESSAGING.md b/MESSAGING.md new file mode 100644 index 0000000..14d49b4 --- /dev/null +++ b/MESSAGING.md @@ -0,0 +1,102 @@ +# hookform-action Messaging Pack + +## Core Positioning +- The missing layer between React Hook Form and your server. +- Typed submit flows with Zod mapping, optimistic UI, persistence, and DevTools. +- Keep RHF ergonomics. Remove repeated integration wiring. + +## 1) One-Liners (15) +1. Typed submit flows for React Hook Form. +2. The missing layer between RHF and your server. +3. Write the schema. Write the action. Skip the wiring. +4. React Hook Form meets typed server submits. +5. Keep RHF. Add reliable server submit flows. +6. One hook for validation, pending state, and submit errors. +7. Zod errors mapped to RHF fields automatically. +8. Optimistic UI with rollback, built in. +9. Persistence for multi-step forms, without storage glue code. +10. Server actions without repetitive form plumbing. +11. End-to-end type safety from input to action result. +12. Predictable RHF submit pipelines for production apps. +13. Same API for Next.js and standalone React apps. +14. Less per-form code between data entry and mutation. +15. Form-server integration you configure, not rewrite. + +## 2) Short Descriptions (10) +1. hookform-action bridges React Hook Form and server submits with typed data flow and automatic field error mapping. +2. It removes repetitive submit plumbing: FormData parsing, transition wiring, and server error propagation. +3. Define your Zod schema once and keep types consistent across validation, submission, and response handling. +4. Enable optimistic updates with rollback through options, not custom state machines. +5. Persist multi-step values with debounced sessionStorage restore behavior. +6. Keep the RHF API you already use while adding server-aware pending and result state. +7. Use the same mental model in Next.js Server Actions and standalone React apps. +8. Add DevTools to inspect form state, submit history, and debug actions during development. +9. Built for teams that need predictable server-backed forms without replacing RHF. +10. A practical typed layer for RHF submit flows with Zod and server feedback. + +## 3) Medium Descriptions (5) +1. hookform-action is the integration layer between React Hook Form and your server. It standardizes typed submission, Zod validation mapping, pending state, and field-level server errors in one pipeline. You keep RHF ergonomics and remove repeated boilerplate. +2. RHF, Zod, and Server Actions are strong primitives, but the space between them is where most form bugs appear. hookform-action closes that gap with typed submit flow control, automatic error mapping, optimistic updates, and optional persistence. +3. Use `withZod` as a single source of truth for validation and types. hookform-action carries those types through the submit lifecycle and keeps form state behavior consistent across client and server boundaries. +4. In Next.js, it plugs into Server Actions directly. In standalone React apps, the same API works with a `submit` function, which keeps form architecture consistent across frameworks. +5. The library is designed for production submit behavior: predictable pending state, rollback-safe optimistic UI, debounced persistence, and DevTools visibility. It reduces per-form complexity while keeping behavior explicit. + +## 4) GitHub Repo Description Options (5) +1. Typed submit flows for React Hook Form: Zod mapping, optimistic UI, persistence, and DevTools. +2. The missing layer between RHF and server submits, with typed actions and automatic field error mapping. +3. Connect RHF to Next.js Server Actions or standalone submits with one consistent API. +4. Server-backed React forms without glue code: typed flow, error mapping, optimistic rollback, persistence. +5. Monorepo for hookform-action core, Next.js/standalone adapters, and DevTools. + +## 5) Headline Variations (5) +1. React Hook Form Meets Typed Server Submits +2. Write the Schema. Write the Action. Skip the Wiring. +3. Typed Submit Flows for Real RHF Apps +4. One Hook Between RHF and Server Mutations +5. Keep React Hook Form. Remove Submit Plumbing. + +## 6) Release Announcement Variations (5) +1. `hookform-action v4.0.3` is live: typed RHF submit flows with Zod mapping, optimistic UI, persistence, and DevTools. +2. Released: `hookform-action v4.0.3`. Same RHF-first API, better server submit consistency. +3. `hookform-action v4.0.3` ships typed form-server wiring so you can stop rewriting the same submit glue code. +4. New release: `hookform-action v4.0.3` for predictable RHF submit pipelines across Next.js and standalone React apps. +5. `hookform-action v4.0.3` is out. If you manually wire RHF + Zod + server submits, this package standardizes that flow. + +## 7) "What This Replaces" Messaging (5) +1. Replaces manual `FormData` parsing and hand-written type casting. +2. Replaces custom `fieldErrors -> setError` mapping code per form. +3. Replaces per-form `useTransition` submit pending wiring. +4. Replaces ad-hoc optimistic update and rollback logic. +5. Replaces custom sessionStorage persistence code for wizard flows. + +## X / Twitter Post Templates (5) +1. React Hook Form + server submits usually means repeated glue code. `hookform-action` gives typed submit flows, Zod error mapping, optimistic UI, persistence, and DevTools. `npm i hookform-action` +2. We built `hookform-action` to remove repetitive RHF submit plumbing: FormData parsing, transition wiring, and field error mapping. Keep RHF, standardize the server flow. +3. `hookform-action v4.0.3` is live. Typed RHF submit flows for Next.js Server Actions and standalone React apps. One API, less boilerplate, clearer behavior. +4. If your RHF forms submit to a server, this is the integration layer: typed actions, Zod mapping, pending state, optimistic rollback, and persistence. +5. `withZod` + `useActionForm`: schema once, typed flow through validation, submit, and response handling. That is the core idea behind `hookform-action`. + +## LinkedIn Post Template (1) +Today we are shipping `hookform-action v4.0.3`. + +React Hook Form, Zod, and Server Actions are solid primitives. The repeated work is in the integration layer between them: submit wiring, error mapping, pending state, and recovery behavior. + +`hookform-action` standardizes that layer with typed submit flows, automatic Zod -> RHF field mapping, optimistic UI with rollback, multi-step persistence, and DevTools. + +It works with Next.js Server Actions and also with standalone React apps through the same API shape. + +If your team is rewriting form-server glue code in every feature, this release is for you. + +## Release Notes Snippets (5) +1. This release strengthens the project positioning around typed submit flows for React Hook Form and server integrations. +2. Messaging now emphasizes practical outcomes: less submit boilerplate, predictable pending state, and consistent field error mapping. +3. Documentation copy is now aligned across README, docs home, and package descriptions for clearer onboarding. +4. NPM metadata has been updated to surface Zod mapping, optimistic UI, persistence, and DevTools in a consistent way. +5. A centralized messaging pack has been added to support launch posts, release notes, and changelog summaries. + +## Changelog Highlights (5) +1. Refined core tagline to "typed submit flows for React Hook Form". +2. Updated docs home messaging to match the current product scope and tone. +3. Aligned package descriptions across `hookform-action`, `-standalone`, `-core`, and `-devtools`. +4. Improved README wording to reinforce technical clarity over marketing phrasing. +5. Added reusable copy blocks for GitHub, npm, X/Twitter, LinkedIn, release notes, and changelog highlights. diff --git a/README.md b/README.md index 7fa9292..8b67170 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,25 @@

⚡ hookform-action

-    Seamless integration between React Hook Form and any React framework
-    with Zod validation, automatic type inference, optimistic UI, multi-step persistence, and DevTools. -
- 📚 Explore the Docs »

+ The missing layer between React Hook Form and your server.
+ Typed submit flows, automatic Zod error mapping, optimistic UI with rollback, persistence, and DevTools —
+ for Next.js Server Actions, Vite, Remix, Astro, or any React app. +

+

+ 📚 Documentation +  ·  + Quick Start +  ·  + Choose Your Path +  ·  + Examples +  ·  + FAQ +  ·  + API Reference +  ·  + Packages +

@@ -19,83 +33,211 @@ ## The Problem -Connecting React Hook Form with server-side actions requires tons of boilerplate: manual FormData conversion, error mapping, state management, and type juggling. +React Hook Form handles form state beautifully, but the moment you connect it to a server — a Next.js Server Action, a REST endpoint, a Remix action — you end up writing the same boilerplate every single time: -**hookform-action** solves this in one hook — for **Next.js Server Actions**, **Vite SPAs**, **Remix**, **Astro**, or any React app. +- Manually serialize form values to `FormData` or JSON +- Wire `useTransition` or `useFormState` to track pending state +- Parse Zod errors from the server response and map them back to individual fields +- Handle `prevState` for progressive enhancement +- Roll back UI state on failure -## Packages +That is hundreds of lines of plumbing that has nothing to do with your actual business logic. -| Package | Description | -| --------------------------------------------------- | ------------------------------------ | -| [`hookform-action-core`](packages/core) | Core library (framework-agnostic) | -| [`hookform-action`](packages/next) | Next.js adapter (⭐ main install) | -| [`hookform-action-standalone`](packages/standalone) | Adapter for Vite, Remix, Astro, SPAs | -| [`hookform-action-devtools`](packages/devtools) | Floating debug panel (FormDevTool) | +**hookform-action gives you one typed hook that handles all of it.** -## What's New in v3 +--- -- 🌍 **Framework-Agnostic Core** — Core decoupled from Next.js. Use with any React framework -- 🚀 **Standalone Adapter** — `hookform-action-standalone` for Vite, Remix, Astro, or any React SPA -- 🔍 **DevTools Panel** — `hookform-action-devtools` for real-time form state inspection -- 🧩 **Internal Plugin System** — Lifecycle hooks (`onBeforeSubmit`, `onSuccess`, `onError`, `onMount`) -- 🔄 **Zero Breaking Changes** — Existing `hookform-action` imports work identically +## Why hookform-action? -## Features +| Concern | Without hookform-action | With hookform-action | +| ---------------------- | ------------------------------------------ | ----------------------------------- | +| Type safety | Manual casts from `FormData` | Full inference from your Zod schema | +| Error mapping | Parse JSON → iterate fields → `setError()` | Automatic, zero config | +| Pending state | `useTransition` + manual boolean | `formState.isPending` | +| Optimistic UI | Custom `useOptimistic` wiring | `optimisticKey` + `optimisticData` | +| Client validation | Duplicate schema setup | Auto-detected from `withZod` | +| Multi-step persistence | Roll your own sessionStorage | `persistKey` + `persistDebounce` | +| Debugging | `console.log` everywhere | `` panel | -- 🔒 **Full Type Inference** — Types inferred from your action automatically -- ⚡ **Auto Error Mapping** — Zod `.flatten().fieldErrors` mapped to RHF fields out of the box -- 🚀 **Optimistic UI** — Native `useOptimistic` integration with automatic rollback -- 🔍 **Client-Side Validation** — Real-time Zod validation (`onChange`/`onBlur`/`onSubmit`) -- 💾 **Wizard Persistence** — Multi-step form state saved to sessionStorage with debounce -- 🧩 **Headless `

`** — Optional wrapper providing FormContext to children -- 📦 **Tiny Bundle** — ESM + CJS, tree-shakeable, peer deps only -- 🧪 **81+ Tests** — Vitest + React Testing Library +--- -## Installation +## Before & After -### Next.js (Server Actions) +### Without hookform-action -```bash -npm install hookform-action react-hook-form zod -``` +```tsx +// ❌ Manual wiring — ~60 lines to do what one hook does +"use client"; +import { useForm } from "react-hook-form"; +import { useTransition } from "react"; + +export function LoginForm() { + const [isPending, startTransition] = useTransition(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm(); -### Vite / Remix / Astro (Standalone) + const onSubmit = (values) => { + startTransition(async () => { + const formData = new FormData(); + Object.entries(values).forEach(([k, v]) => formData.append(k, String(v))); -```bash -npm install hookform-action-standalone react-hook-form zod + const result = await loginAction(null, formData); + + if (!result.success && result.errors) { + Object.entries(result.errors).forEach(([field, messages]) => { + setError(field, { message: messages[0] }); + }); + } + }); + }; + + return ( + + + {errors.email && {errors.email.message}} + +
+ ); +} ``` -### DevTools (optional) +### With hookform-action -```bash -npm install hookform-action-devtools +```tsx +// ✅ One hook, fully typed, zero boilerplate +"use client"; +import { useActionForm } from "hookform-action"; +import { loginAction } from "./actions"; + +export function LoginForm() { + const { + register, + handleSubmit, + formState: { errors, isPending }, + } = useActionForm(loginAction, { validationMode: "onChange" }); + + return ( +
+ + {errors.email && {errors.email.message}} + +
+ ); +} ``` +--- + +## Packages + +hookform-action is a monorepo. Install the adapter that matches your stack. + +| Package | Install | Description | +| --------------------------------------------------- | ---------------------------------- | --------------------------------------------------------------------- | +| [`hookform-action`](packages/next) | `npm i hookform-action` | **Next.js adapter** — Server Actions, FormData, `prevState` | +| [`hookform-action-standalone`](packages/standalone) | `npm i hookform-action-standalone` | **Standalone adapter** — fetch, axios, Remix, Vite, Astro | +| [`hookform-action-core`](packages/core) | internal | Framework-agnostic core — `useActionFormCore`, `withZod`, persistence | +| [`hookform-action-devtools`](packages/devtools) | `npm i hookform-action-devtools` | Floating debug panel — form state, submission history | + +> **Zod and react-hook-form are peer dependencies.** Install them alongside any adapter: +> +> ```bash +> npm install hookform-action react-hook-form zod +> ``` + +--- + +## Choose Your Path + +Pick the shortest adoption route for your stack: + +| You are using | Install | Start here | +| --- | --- | --- | +| Next.js App Router + Server Actions | `npm i hookform-action react-hook-form zod` | [Quick Start - Next.js](#quick-start--nextjs) | +| Vite / Remix / Astro / SPA | `npm i hookform-action-standalone react-hook-form zod` | [Quick Start - Standalone](#quick-start--standalone-vite-remix-astro) | +| Custom adapter / framework integration | `npm i hookform-action-core react-hook-form zod` | [How It Works](#how-it-works) | + +--- + +## Examples that show real value + +These are the examples that convert fastest for new users: + +- Login / registration with server validation + field error mapping +- Client-side validation with a shared schema (no duplication) +- Optimistic UI with rollback on action failure +- Multi-step wizard with draft persistence + +Live docs pages: + +- https://hookform-action-docs.vercel.app/examples +- https://hookform-action-docs.vercel.app/recipes + +--- + +## What you stop writing + +- ❌ Manual `FormData` → typed object conversion +- ❌ `.flatten().fieldErrors` → `setError()` mapping +- ❌ Duplicate Zod passes for client-side validation +- ❌ `useTransition` / `startTransition` wiring +- ❌ `useOptimistic` setup and rollback logic +- ❌ `sessionStorage` wiring for multi-step wizards + +## What you get + +- ✅ **Full type inference** — types flow from your Zod schema through `withZod` into the hook with no manual generics +- ✅ **Auto error mapping** — server-side Zod errors (`flatten().fieldErrors`) are automatically applied to RHF fields +- ✅ **Client-side validation** — real-time validation using the same schema, with `onChange`, `onBlur`, or `onSubmit` modes +- ✅ **Optimistic UI** — native `useOptimistic` (React 19) with automatic rollback and a React 18 fallback +- ✅ **Wizard persistence** — multi-step form state survives page refreshes via sessionStorage with debounce +- ✅ **Headless `
`** — optional context provider that distributes form state to any child component +- ✅ **DevTools** — floating panel with live state, submission history, and debug actions; zero CSS dependencies +- ✅ **Plugin system** — lifecycle hooks (`onBeforeSubmit`, `onSuccess`, `onError`, `onMount`) for custom integrations +- ✅ **Tiny footprint** — ESM + CJS, tree-shakeable, only peer deps; no runtime bloat +- ✅ **81+ tests** — Vitest + React Testing Library + +--- + ## Quick Start — Next.js -### 1. Create a Server Action +### 1. Install + +```bash +npm install hookform-action react-hook-form zod +``` + +### 2. Create a Server Action ```ts -// app/actions.ts +// app/login/actions.ts "use server"; import { z } from "zod"; import { withZod } from "hookform-action-core/with-zod"; const schema = z.object({ - email: z.string().email(), - password: z.string().min(8), + email: z.string().email("Invalid email"), + password: z.string().min(8, "At least 8 characters"), }); export const loginAction = withZod(schema, async (data) => { - // data is typed as { email: string; password: string } + // `data` is fully typed as { email: string; password: string } + const user = await db.authenticate(data.email, data.password); + if (!user) return { success: false, errors: { email: ["Invalid credentials"] } }; return { success: true }; }); ``` -### 2. Use the Hook +> `withZod` validates on the server, maps Zod errors to the flat `{ errors: Record }` shape, and attaches the schema to the action so the hook can auto-detect it for client-side validation. + +### 3. Use the Hook ```tsx -// app/login-form.tsx +// app/login/login-form.tsx "use client"; import { useActionForm } from "hookform-action"; import { loginAction } from "./actions"; @@ -104,45 +246,59 @@ export function LoginForm() { const { register, handleSubmit, - formState: { errors, isPending }, + formState: { errors, isPending, isSubmitSuccessful }, } = useActionForm(loginAction, { defaultValues: { email: "", password: "" }, - validationMode: "onChange", + validationMode: "onChange", // schema auto-detected from withZod + onSuccess: () => redirect("/dashboard"), }); return ( - - {errors.email && {errors.email.message}} - - - {errors.password && {errors.password.message}} - - +
+ + {errors.email &&

{errors.email.message}

} +
+ +
+ + {errors.password &&

{errors.password.message}

} +
+ + ); } ``` -## Quick Start — Standalone (Vite, Remix, etc.) +--- + +## Quick Start — Standalone (Vite, Remix, Astro) + +```bash +npm install hookform-action-standalone react-hook-form zod +``` ```tsx +// components/contact-form.tsx import { useActionForm } from "hookform-action-standalone"; import { z } from "zod"; const schema = z.object({ email: z.string().email(), - password: z.string().min(8), + message: z.string().min(10), }); -export function LoginForm() { +export function ContactForm() { const { register, handleSubmit, - formState: { errors, isPending }, + formState: { errors, isPending, isSubmitSuccessful }, } = useActionForm({ submit: async (data) => { - const res = await fetch("/api/login", { + const res = await fetch("/api/contact", { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, @@ -150,211 +306,446 @@ export function LoginForm() { return res.json(); }, schema, - validationMode: "onChange", - defaultValues: { email: "", password: "" }, + validationMode: "onBlur", + defaultValues: { email: "", message: "" }, }); + if (isSubmitSuccessful) return

Message sent!

; + return (
- - {errors.email && {errors.email.message}} + + {errors.email &&

{errors.email.message}

} - - {errors.password && {errors.password.message}} +