React Hook Form
@contract-kit/react-hook-form creates typed React Hook Form options from a contract body schema. Use it when a form submits to a contract and should reuse the same validation rules on the client.
bun add @contract-kit/react-hook-form react-hook-form @hookform/resolvers
Setup
import { rhf } from "@contract-kit/react-hook-form";
import { createTodo } from "@/app/contracts/todo";
const createTodoForm = rhf(createTodo);
Basic form
function CreateTodoForm() {
const form = createTodoForm.useForm({
defaultValues: { title: "", completed: false },
});
const onSubmit = form.handleSubmit(async (data) => {
await createTodoEndpoint.call({ body: data });
});
return (
<form onSubmit={onSubmit}>
<input {...form.register("title")} />
{form.formState.errors.title && (
<span>{form.formState.errors.title.message}</span>
)}
<button type="submit" disabled={form.formState.isSubmitting}>
Create
</button>
</form>
);
}
Validation runs automatically using your contract's body schema. Field names, values, and error messages are inferred from the contract.
With React Query
function CreateTodoForm() {
const form = createTodoForm.useForm({
defaultValues: { title: "", completed: false },
});
const mutation = useMutation(
rq(createTodo).mutationOptions({
onSuccess: () => form.reset(),
onError: (error) => {
form.setError("root", {
message: error.body?.message ?? error.message,
});
},
}),
);
const onSubmit = form.handleSubmit((data) => {
mutation.mutate({ body: data });
});
return (
<form onSubmit={onSubmit}>
<input {...form.register("title")} />
{form.formState.errors.title && (
<span>{form.formState.errors.title.message}</span>
)}
{form.formState.errors.root && (
<span>{form.formState.errors.root.message}</span>
)}
<button type="submit" disabled={mutation.isPending}>
Create
</button>
</form>
);
}
Form options
Get raw form options if you want to call useForm yourself.
import { useForm } from "react-hook-form";
const form = useForm(
createTodoForm.formOptions({
defaultValues: { title: "" },
mode: "onBlur",
}),
);
Disable automatic validation
Set resolverEnabled to false when you want React Hook Form typing without the schema resolver.
const form = createTodoForm.useForm({
resolverEnabled: false,
});