skills/form-patterns/SKILL.md
Project-specific form patterns using useAppForm, createFormSubmitHandler, and createApiResponseHandlerEffect. Use when building or editing any form with validation, submission, or API response handling in React.
npx skillsauth add bkinsey808/songshare-effect form-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Requires: file-read, terminal (linting/testing). No network access needed.
Use this skill when:
useAppForm and Effect helpers.Execution workflow:
useAppForm as the primary form abstraction.npm run lint.Output requirements:
All forms in this project use useAppForm from @/react/lib/form/useAppForm. Do not reach for raw React state + onSubmit — the project has typed Effect/validation plumbing you must use.
useAppForm Hookimport useAppForm from "@/react/lib/form/useAppForm";
import { Effect } from "effect";
import { useRef } from "react";
import { MyFormSchema } from "./myFormSchema"; // Effect Schema
function MyForm(): ReactElement {
const formRef = useRef<HTMLFormElement>(null);
const {
validationErrors,
isSubmitting,
handleFieldBlur,
getFieldError,
handleSubmit,
handleApiResponseEffect,
reset,
} = useAppForm({
schema: MyFormSchema,
formRef,
defaultErrorMessage: "Something went wrong. Please try again.",
});
// ...
}
| Prop | Type | Required | Description |
| --------------------- | ---------------------------------- | -------- | ------------------------------- |
| schema | Schema.Schema<FormValues> | ✅ | Effect Schema for validation |
| formRef | React.RefObject<HTMLFormElement> | ✅ | Ref to the <form> element |
| defaultErrorMessage | string | optional | Fallback for generic API errors |
| initialValues | Partial<FormValues> | optional | Values used by reset() |
handleSubmit returns an Effect.Effect<void>. Run it with Effect.runFork or Effect.runPromise:
function handleFormSubmit(formData: Record<string, unknown>): void {
Effect.runFork(
handleSubmit(formData, async (validatedData) => {
const response = await fetch("/api/songs", {
method: "POST",
body: JSON.stringify(validatedData),
});
await Effect.runPromise(
handleApiResponseEffect(response, setSubmitError),
);
}),
);
}
// Wire to the <form>:
<form
ref={formRef}
onSubmit={(e) => {
e.preventDefault();
handleFormSubmit(Object.fromEntries(new FormData(e.currentTarget)));
}}
>
handleSubmit automatically:
isSubmitting = true while runningvalidationErrors on failureonSubmit(validatedData) on successValidate individual fields as the user leaves them:
<input
name="title"
ref={titleRef}
onBlur={() => handleFieldBlur("title", titleRef)}
/>
{getFieldError("title") && (
<p>{getFieldError("title")?.message}</p>
)}
handleApiResponseEffect maps API responses to form errors:
const success = await Effect.runPromise(handleApiResponseEffect(response, setSubmitError));
// success === true → API returned OK
// success === false → ApiResponseAction already dispatched (field error or general error)
The API must return one of these JSON error shapes for field/general errors to be routed correctly:
{ "type": "setFieldError", "field": "email", "message": "Already in use" }
{ "type": "setGeneralError", "message": "Server error" }
Use Effect Schema in a colocated <feature>FormSchema.ts file:
// react/src/song/SongFormSchema.ts
import { Schema } from "effect";
export const SongFormSchema = Schema.Struct({
title: Schema.String.pipe(Schema.minLength(1)),
artist: Schema.String.pipe(Schema.minLength(1)),
});
export type SongFormValues = Schema.Schema.Type<typeof SongFormSchema>;
useState for validation errors in forms — use useAppForme.target.value directly in submit handlers — read from FormData or the schema-validated objecttry/catch for form validation — handleSubmit handles ituseAppForm with React Hook Form or other form libraries@/react/lib/form/useAppForm.tseffect-ts-patterns.react-best-practices.tools
Zustand state management patterns for this project — store creation, selectors, Immer middleware, async actions with loading states, devtools, persist, and testing. Use when authoring or editing Zustand stores (use*Store files) or components that subscribe to stores. Do NOT use for React component structure or TypeScript-only utilities.
testing
How to write, update, or split skill files in this repo. Use when creating a new SKILL.md, updating an existing one, or deciding whether to put content in a skill vs. docs/.
development
Complete guide for testing React hooks — renderHook, Documentation by Harness, installStore, fixtures, subscription patterns, lint/compiler traps, and pre-completion checklist. Read docs/testing/unit-test-hook-best-practices.md for the full reference.
development
Vitest unit test authoring for this repo — setup, mocking, API handler testing, and common pitfalls for non-hook code. Use when the user asks to add, update, fix, or review unit tests for utilities, components, API handlers, or scripts. Do NOT use for React hook tests — load unit-test-hook-best-practices instead.