@heapform/react
v0.0.1
Published
Headless React helpers for submitting forms to Heapform.
Readme
@heapform/react
Headless React helpers for submitting forms to Heapform.
Basic usage
import { HeapformValidationError, useHeapform } from "@heapform/react";
export function ContactForm() {
const [state, handleSubmit, reset] = useHeapform("contact");
if (state.succeeded) {
return (
<div>
<p>Thanks for your message.</p>
<button type="button" onClick={reset}>
Send another
</button>
</div>
);
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" required />
<HeapformValidationError field="email" errors={state.errors} prefix="Email" />
<label htmlFor="message">Message</label>
<textarea id="message" name="message" required />
<HeapformValidationError field="message" errors={state.errors} />
<HeapformValidationError errors={state.errors} />
<button disabled={state.submitting} type="submit">
{state.submitting ? "Sending..." : "Send"}
</button>
</form>
);
}State object
useHeapform returns [state, handleSubmit, reset]:
| Key | Description |
| --- | --- |
| state.submitting | Whether a submission is in flight |
| state.succeeded | Whether the last submission succeeded |
| state.errors | A HeapformSubmissionError instance, or null |
| state.result | Success metadata such as returnUrl, or null |
HeapformSubmissionError exposes:
getFormErrors()for form-level errorsgetFieldErrors(field)for a single fieldgetAllFieldErrors()for all field errors
React Hook Form
Use useSubmit when another form library owns validation state:
import { useSubmit } from "@heapform/react";
import { useForm } from "react-hook-form";
type Inputs = {
email: string;
message: string;
};
export function ContactForm() {
const { formState, handleSubmit, register, setError } = useForm<Inputs>();
const submit = useSubmit<Inputs>("contact", {
onError(errors) {
for (const [field, fieldErrors] of errors.getAllFieldErrors()) {
setError(field as keyof Inputs, {
message: fieldErrors.map((error) => error.message).join(", "),
});
}
for (const { code, message } of errors.getFormErrors()) {
setError("root", { type: code, message });
}
},
});
return (
<form onSubmit={handleSubmit(submit)}>
<input {...register("email")} type="email" />
<textarea {...register("message")} />
<button disabled={formState.isSubmitting} type="submit">
Send
</button>
</form>
);
}Next.js App Router
Next.js helpers live behind an optional subpath export so the root package stays framework-neutral:
import {
getHeapformActionErrors,
initialHeapformActionState,
submitHeapformAction,
type HeapformActionState,
} from "@heapform/react/next";Use submitHeapformAction inside your own Server Action:
// app/contact/actions.ts
"use server";
import {
submitHeapformAction,
type HeapformActionState,
} from "@heapform/react/next";
export async function contactAction(
_previousState: HeapformActionState,
formData: FormData,
) {
return submitHeapformAction("contact", formData);
}Then wire it to useActionState:
// app/contact/form.tsx
"use client";
import { useActionState } from "react";
import { HeapformValidationError } from "@heapform/react";
import {
getHeapformActionErrors,
initialHeapformActionState,
} from "@heapform/react/next";
import { contactAction } from "./actions";
export function ContactForm() {
const [state, formAction, isPending] = useActionState(
contactAction,
initialHeapformActionState,
);
const errors = getHeapformActionErrors(state);
if (state.succeeded) {
return <p>Thanks for your message.</p>;
}
return (
<form action={formAction}>
<input name="email" type="email" required />
<HeapformValidationError field="email" errors={errors} prefix="Email" />
<textarea name="message" required />
<HeapformValidationError errors={errors} />
<button disabled={isPending} type="submit">
Send
</button>
</form>
);
}For a Server Component form that does not need client-side action state:
import { submitHeapformAction } from "@heapform/react/next";
export function ContactForm() {
async function action(formData: FormData) {
"use server";
await submitHeapformAction("contact", formData);
}
return (
<form action={action}>
<input name="email" type="email" required />
<button type="submit">Send</button>
</form>
);
}