@rowen-forms/react
React hooks and components for Rowen forms. Build type-safe, accessible forms with built-in validation, submission handling, and error display — no boilerplate required.
Installation
Install the package from npm.
npm install @rowen-forms/react
Or with other package managers:
yarn add @rowen-forms/react pnpm add @rowen-forms/react
Quick start
A complete contact form in under 30 lines. The useForm hook handles submission, loading state, and server-side validation errors.
import { useForm, ValidationError } from "@rowen-forms/react";
function ContactForm() {
const { state, handleSubmit, getFieldProps } = useForm("YOUR_FORM_ID");
if (state.succeeded) {
return <p>Thanks for your message! We'll be in touch.</p>;
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input
id="email"
{...getFieldProps("email", { required: true, type: "email" })}
/>
<ValidationError field="email" errors={state.errors} />
<label htmlFor="message">Message</label>
<textarea id="message" {...getFieldProps("message")} />
<ValidationError field="message" errors={state.errors} />
<button type="submit" disabled={state.submitting}>
{state.submitting ? "Sending..." : "Send"}
</button>
</form>
);
}API Reference
useForm(formId, options?)
The main hook. Pass your Rowen form ID and an optional config object. Returns everything you need to render and submit a form.
Parameters
| Param | Type | Description |
|---|---|---|
| formId | string | Required. Your Rowen form ID (from the dashboard). |
| options.endpoint | string | Override the API endpoint. Defaults to https://rowen.in/api/f/{formId}. |
| options.onSuccess | (data) => void | Callback fired after a successful submission. Receives the API response. |
| options.onError | (errors) => void | Callback fired when the submission fails or returns validation errors. |
| options.data | Record<string, string> | Extra hidden fields to include with every submission (e.g. page URL, user ID). |
Return value
| Property | Type | Description |
|---|---|---|
| state.submitting | boolean | true while the form is being submitted. |
| state.succeeded | boolean | true after a successful submission. Use this to show a thank-you message. |
| state.errors | FieldError[] | Array of validation errors returned by the server. Pass to ValidationError. |
| handleSubmit | (e: FormEvent) => void | Pass to your <form onSubmit>. Prevents default, serializes fields, and posts to Rowen. |
| getFieldProps | (name, opts?) => props | Returns name, id, type, required, and aria-describedby props for an input. Spread onto <input> or <textarea>. |
| reset | () => void | Resets the form state (clears errors, succeeded, and submitting flags). |
| submitData | (data: object) => Promise | Submit data programmatically without a form element. Useful for custom UIs or multi-step flows. |
<ValidationError>
Renders inline validation errors for a specific field. Automatically matches errors from state.errors by field name. Renders nothing when there are no errors for the field.
| Prop | Type | Description |
|---|---|---|
| field | string | Required. The name of the field to show errors for. |
| errors | FieldError[] | Required. Pass state.errors from useForm. |
| className | string | Optional CSS class for custom styling. |
| as | string | Component | Wrapper element. Defaults to span. |
<input {...getFieldProps("email", { required: true, type: "email" })} />
<ValidationError
field="email"
errors={state.errors}
className="text-red-500 text-sm"
/><RowenProvider>Optional
Wrap your app in RowenProvider to set a default form ID or endpoint for all nested useFormcalls. This is optional — you can always pass the form ID directly to the hook.
import { RowenProvider } from "@rowen-forms/react";
function App() {
return (
<RowenProvider formId="YOUR_FORM_ID">
<ContactForm />
</RowenProvider>
);
}Next.js example
Since useForm uses React hooks, you need the "use client" directive in Next.js App Router.
"use client";
import { useForm, ValidationError } from "@rowen-forms/react";
export default function ContactPage() {
const { state, handleSubmit, getFieldProps } = useForm("YOUR_FORM_ID");
if (state.succeeded) {
return (
<div className="text-center py-12">
<h2>Message sent!</h2>
<p>We'll get back to you soon.</p>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="max-w-md mx-auto space-y-4">
<div>
<label htmlFor="email">Email</label>
<input
id="email"
{...getFieldProps("email", { required: true, type: "email" })}
className="w-full border rounded px-3 py-2"
/>
<ValidationError field="email" errors={state.errors} />
</div>
<div>
<label htmlFor="message">Message</label>
<textarea
id="message"
{...getFieldProps("message")}
className="w-full border rounded px-3 py-2"
rows={4}
/>
<ValidationError field="message" errors={state.errors} />
</div>
<button
type="submit"
disabled={state.submitting}
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 disabled:opacity-50"
>
{state.submitting ? "Sending..." : "Send Message"}
</button>
</form>
);
}Programmatic submission
Use submitData to submit form data without a traditional <form> element. This is useful for custom UIs, multi-step wizards, or chatbot flows.
import { useForm } from "@rowen-forms/react";
function FeedbackWidget() {
const { state, submitData } = useForm("YOUR_FORM_ID");
const sendFeedback = (rating: number) => {
submitData({
rating: String(rating),
page: window.location.href,
});
};
if (state.succeeded) return <p>Thanks for your feedback!</p>;
return (
<div>
<p>How would you rate this page?</p>
{[1, 2, 3, 4, 5].map((n) => (
<button
key={n}
onClick={() => sendFeedback(n)}
disabled={state.submitting}
>
{n}
</button>
))}
</div>
);
}Custom callbacks
Use onSuccess and onErrorto run custom logic after submission — analytics events, redirects, toast notifications, etc.
import { useForm, ValidationError } from "@rowen-forms/react";
function ContactForm() {
const { state, handleSubmit, getFieldProps } = useForm("YOUR_FORM_ID", {
onSuccess: (data) => {
// Track conversion
analytics.track("form_submitted", { formId: data.id });
// Redirect after a short delay
setTimeout(() => window.location.href = "/thank-you", 1500);
},
onError: (errors) => {
console.error("Submission failed:", errors);
toast.error("Something went wrong. Please try again.");
},
data: {
// Hidden fields included with every submission
_source: "website",
_page: typeof window !== "undefined" ? window.location.href : "",
},
});
if (state.succeeded) return <p>Thanks! Redirecting...</p>;
return (
<form onSubmit={handleSubmit}>
<input {...getFieldProps("email", { required: true, type: "email" })} />
<ValidationError field="email" errors={state.errors} />
<textarea {...getFieldProps("message")} />
<button type="submit" disabled={state.submitting}>
{state.submitting ? "Sending..." : "Send"}
</button>
</form>
);
}TypeScript types
The package ships with full TypeScript declarations. Here are the main types you can import:
import type {
UseFormOptions, // Options for useForm(formId, options)
FormState, // { submitting, succeeded, errors }
FieldError, // { field: string; message: string; code?: string }
UseFormReturn, // Return type of useForm()
} from "@rowen-forms/react";getFieldPropssetsaria-describedbyautomatically for accessibility- The honeypot spam field is included automatically — no extra setup needed
- Works with React 18+ and Next.js 13+ (App Router and Pages Router)
- The package is tree-shakeable — only import what you use
- For the popup widget approach (no form building), see the Widget docs