@cogs/react-hook-form
v0.2.0
Published
React Hook Form helpers — selective dirty/error subscriptions, deep default merging, and controlled-field utilities.
Readme
@cogs/react-hook-form
Utilities that extend React Hook Form with deterministic resets, selective dirty/error detection, row-aware state helpers, and typed resolver exports so complex form workflows stay fast and predictable.
Features
- One-stop re-export for
react-hook-formand@hookform/resolvers/zod(zodResolver). - Field-array ergonomics via
useControlledFields,useRowStatuses, and hierarchy-aware status helpers to prevent UI flicker and track per-row lifecycle. - Selective observers (
useSelectiveDirty,useSelectiveErrors,useOptimizedFormState) to minimize unnecessary rerenders. - Deterministic default management with
mergeDefaultsDeep,makeRHFEmptyUndefined,rhfResetUndefined, and deep dirty extraction utilities (extractDirtyValues,inflateDirty). - Validation orchestration through
runValidationStepsplus targeted value sync (useSyncFormFields) for complex wizard flows. - Location/mirror helpers (
lookupLocationTarget,lookupLocationTargetFlat,buildIdToPathMirrorMap,updateRows) for hierarchical field arrays.
Technology Stack
- TypeScript 5, native ECMAScript modules (NodeNext), and React 19 peer integration.
- React Hook Form 7 with
@hookform/resolversfor schema validation. tscfor declaration + ESM output; Vitest (jsdom) for unit tests.
The small set of object helpers this package depends on (get, isPlainObject, isEmptyObject, and the HierarchyRow type) are inlined under src/internal/ so the package has no shared-lib dependency.
Installation
pnpm add @cogs/react-hook-form react-hook-form @hookform/resolversreact and react-dom are optional peer dependencies (only the hook exports require them).
Usage
JavaScript
import React from "react"
import {
useForm,
useFieldArray,
useSelectiveDirty,
useControlledFields,
} from "@cogs/react-hook-form"
export function ServiceRowsEditor({ defaultValues }) {
const form = useForm({ defaultValues })
const { control } = form
const { fields } = useFieldArray({ control, name: "services" })
const watchedRows = form.watch("services")
const controlledFields = useControlledFields(fields, watchedRows)
const hasServiceChanges = useSelectiveDirty(control, [
"services.*.name",
"services.*.pinnedSHA",
])
return (
<form onSubmit={form.handleSubmit(console.log)}>
{controlledFields.map((field, index) => (
<div key={field.id}>
<input {...form.register(`services.${index}.name`)} />
<input {...form.register(`services.${index}.pinnedSHA`)} />
</div>
))}
<button type="submit" disabled={!hasServiceChanges}>
Save Updates
</button>
</form>
)
}TypeScript
import type { Path, FieldValues } from "@cogs/react-hook-form"
import {
mergeDefaultsDeep,
makeRHFEmptyUndefined,
runValidationSteps,
type ValidationField,
type FriendlyValidationMap,
} from "@cogs/react-hook-form"
type CampaignForm = {
name: string
budget: { amount: number; currency: string }
schedule: { startDate: string; endDate: string }
}
export async function validateStep(
trigger: (name: Path<CampaignForm>) => Promise<boolean>,
errors: FieldValues,
) {
const fields: ValidationField<CampaignForm>[] = [
{ name: "name" },
{ name: "budget", lookup: "amount" },
]
const friendly: FriendlyValidationMap<CampaignForm> = {
name: "Campaign Name",
budget: "Budget Amount",
}
return runValidationSteps(trigger, fields, errors, friendly)
}
export function mergeDefaults(target: CampaignForm, source: Partial<CampaignForm>) {
const reset = makeRHFEmptyUndefined(target)
return mergeDefaultsDeep(reset, source)
}Scripts
pnpm build # tsc -p tsconfig.json (ESM + d.ts)
pnpm typecheck # tsc --noEmit
pnpm test # vitest run (jsdom)
pnpm lint # biome check .Tests live under src/__tests__ and run against a jsdom environment configured in vitest.config.ts.
