@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.

Terminal
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.

React
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

ParamTypeDescription
formIdstringRequired. Your Rowen form ID (from the dashboard).
options.endpointstringOverride the API endpoint. Defaults to https://rowen.in/api/f/{formId}.
options.onSuccess(data) => voidCallback fired after a successful submission. Receives the API response.
options.onError(errors) => voidCallback fired when the submission fails or returns validation errors.
options.dataRecord<string, string>Extra hidden fields to include with every submission (e.g. page URL, user ID).

Return value

PropertyTypeDescription
state.submittingbooleantrue while the form is being submitted.
state.succeededbooleantrue after a successful submission. Use this to show a thank-you message.
state.errorsFieldError[]Array of validation errors returned by the server. Pass to ValidationError.
handleSubmit(e: FormEvent) => voidPass to your <form onSubmit>. Prevents default, serializes fields, and posts to Rowen.
getFieldProps(name, opts?) => propsReturns name, id, type, required, and aria-describedby props for an input. Spread onto <input> or <textarea>.
reset() => voidResets the form state (clears errors, succeeded, and submitting flags).
submitData(data: object) => PromiseSubmit 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.

PropTypeDescription
fieldstringRequired. The name of the field to show errors for.
errorsFieldError[]Required. Pass state.errors from useForm.
classNamestringOptional CSS class for custom styling.
asstring | ComponentWrapper element. Defaults to span.
React
<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.

React
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.

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.

React
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.

React
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:

TypeScript
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";
UseFormOptions
Configuration object for useForm (endpoint, callbacks, extra data)
FormState
Current state: submitting, succeeded, and errors array
FieldError
Individual error: field name, message, and optional code
UseFormReturn
Full return type including state, handleSubmit, getFieldProps, reset, submitData
Tips:
  • getFieldProps sets aria-describedby automatically 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