---
title: "Add forms to React"
description: "Add a form backend to your React application. Forminit handles submissions, validation, file uploads, and notifications."
url: https://forminit.com/docs/react/
---

# Add forms to React

Add a form backend to your React application. Forminit handles submissions, validation, file uploads, and notifications.

> **Build with AI:** give your AI coding tool your Form ID and this integration skill guide: https://forminit.com/skills/forminit-react/SKILL.md

---

## Getting Started

Submit forms directly from your React components — no backend required. Forminit validates and stores every submission for you.

### Prerequisites

1. Create a Forminit account at [forminit.com](https://forminit.com)
2. Create a form in your dashboard
3. Set authentication mode to **Public** in Form Settings

### Step 1: Install the SDK

```bash
# npm
npm install forminit

# yarn
yarn add forminit

# pnpm
pnpm add forminit
```

### Step 2: Create a Form Component

The example below packs **every block type** into a single component so you can see the full naming pattern in one place. Every input name follows `fi-{blockType}-{name}` — that's the only rule. Most forms only need a handful of these, so keep the blocks you need and delete the rest.

```tsx
import { useState, FormEvent } from 'react';
import { Forminit } from 'forminit';

const forminit = new Forminit();
const FORM_ID = 'YOUR_FORM_ID';

export function ContactForm() {
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const [error, setError] = useState<string | null>(null);

  async function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus('loading');
    setError(null);

    const form = e.currentTarget;
    const formData = new FormData(form);

    const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);

    if (error) {
      setStatus('error');
      setError(error.message);
      return;
    }

    setStatus('success');
    form.reset();
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* sender: who's submitting (built-in, max 1 per submission) */}
      <input type="text"  name="fi-sender-firstName" placeholder="First name" />
      <input type="text"  name="fi-sender-lastName"  placeholder="Last name" />
      {/* Single-field alternative to firstName/lastName */}
      <input type="text"  name="fi-sender-fullName"  placeholder="Full name" />
      <input type="email" name="fi-sender-email"     placeholder="Email" required />
      <input type="text"  name="fi-sender-phone"     placeholder="Phone" />
      <input type="text"  name="fi-sender-title"     placeholder="Title (Mr., Ms., Dr.)" />
      <input type="text"  name="fi-sender-company"   placeholder="Company" />
      <input type="text"  name="fi-sender-position"  placeholder="Job title" />
      <input type="text"  name="fi-sender-address"   placeholder="Address" />
      <input type="text"  name="fi-sender-address2"  placeholder="Address line 2 (apt, suite, etc.)" />
      <input type="text"  name="fi-sender-city"      placeholder="City" />
      <input type="text"  name="fi-sender-postcode"  placeholder="Postcode / ZIP" />
      <input type="text"  name="fi-sender-country"   placeholder="Country (ISO 3166-1 alpha-2, e.g. US)" />
      <input type="hidden" name="fi-sender-userId"   value="ext_12345" />

      {/* text: free-form answer, no validation */}
      <textarea name="fi-text-message" placeholder="Message" />

      {/* number: any numeric value */}
      <input type="number" name="fi-number-budget" placeholder="Budget" />

      {/* email: a second email beyond the sender's */}
      <input type="email" name="fi-email-invitee" placeholder="Invitee email" />

      {/* phone: a second phone beyond the sender's, E.164 format */}
      <input type="text" name="fi-phone-emergency" placeholder="Emergency contact" />

      {/* url: validated as a URL */}
      <input type="url" name="fi-url-website" placeholder="Your website" />

      {/* select: single or multi choice */}
      <select name="fi-select-plan">
        <option value="basic">Basic</option>
        <option value="pro">Pro</option>
        <option value="enterprise">Enterprise</option>
      </select>

      {/* radio: single choice from a group */}
      <label><input type="radio" name="fi-radio-priority" value="low"  /> Low</label>
      <label><input type="radio" name="fi-radio-priority" value="high" /> High</label>

      {/* checkbox: one or more choices, same name = array */}
      <label><input type="checkbox" name="fi-checkbox-features" value="api" />     API</label>
      <label><input type="checkbox" name="fi-checkbox-features" value="support" /> Support</label>

      {/* rating: integer between 1 and 5 */}
      <input type="range" name="fi-rating-satisfaction" min={1} max={5} step={1} defaultValue={3} />

      {/* date: ISO 8601 date or date-time */}
      <input type="date" name="fi-date-appointment" />

      {/* file: requires multipart/form-data (FormData uses it by default) */}
      <input type="file" name="fi-file-resume" />

      {/* country: ISO 3166-1 alpha-2 code */}
      <select name="fi-country-shipping">
        <option value="US">United States</option>
        <option value="GB">United Kingdom</option>
      </select>

      {status === 'error' && <p className="error">{error}</p>}
      {status === 'success' && <p className="success">Message sent successfully!</p>}

      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Sending...' : 'Send'}
      </button>
    </form>
  );
}
```

For a full reference on each block type and its validation rules, see the [Form Blocks documentation](https://forminit.com/docs/form-blocks/).

---

## File Uploads

File uploads require FormData. Add file inputs with the `fi-file-{name}` naming pattern:

```tsx
import { useState, useRef, FormEvent } from 'react';
import { Forminit } from 'forminit';

const forminit = new Forminit();
const FORM_ID = 'YOUR_FORM_ID';

export function ApplicationForm() {
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const [error, setError] = useState<string | null>(null);
  const formRef = useRef<HTMLFormElement>(null);

  async function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus('loading');
    setError(null);

    const formData = new FormData(e.currentTarget);
    const { data, error } = await forminit.submit(FORM_ID, formData);

    if (error) {
      setStatus('error');
      setError(error.message);
      return;
    }

    setStatus('success');
    formRef.current?.reset();
  }

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input
        type="text"
        name="fi-sender-firstName"
        placeholder="First name"
        required
      />
      <input
        type="text"
        name="fi-sender-lastName"
        placeholder="Last name"
        required
      />
      <input
        type="email"
        name="fi-sender-email"
        placeholder="Email"
        required
      />

      <label>Resume (PDF)</label>
      <input
        type="file"
        name="fi-file-resume"
        accept=".pdf,.doc,.docx"
        required
      />

      <label>Portfolio (optional, multiple files)</label>
      <input
        type="file"
        name="fi-file-portfolio[]"
        accept="image/*,.pdf"
        multiple
      />

      {status === 'error' && <p className="error">{error}</p>}
      {status === 'success' && <p className="success">Application submitted!</p>}

      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Submitting...' : 'Submit Application'}
      </button>
    </form>
  );
}
```

> **Important:** When using the `multiple` attribute, append `[]` to the field name (e.g., `fi-file-portfolio[]`).

---

## Response Structure

The SDK returns `{ data, redirectUrl, error }`. On successful submission:

**`data`** contains the submission details:

```json
{
  "hashId": "7LMIBoYY74JOCp1k",
  "date": "2026-01-01 21:10:24",
  "blocks": {
    "sender": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "john.doe@example.com"
    },
    "message": "Hello world"
  }
}
```

**`redirectUrl`** contains the thank you page URL (always returned).

| Field | Type | Description |
|-------|------|-------------|
| `data.hashId` | string | Unique submission identifier |
| `data.date` | string | Submission timestamp (YYYY-MM-DD HH:mm:ss) |
| `data.blocks` | object | All submitted field values |
| `redirectUrl` | string | Thank you page URL |

**Error Response:**

```json
{
  "error": "ERROR_CODE",
  "code": 400,
  "message": "Human-readable error message"
}
```

---

## Redirect After Submission

Every successful submission returns a `redirectUrl`. You configure this URL in **Form Settings → Redirections** — so you can point users to a different thank-you page anytime **without changing your code or redeploying**. Just read `redirectUrl` from the response and send the user there:

```tsx
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
  e.preventDefault();

  const formData = new FormData(e.currentTarget);
  const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);

  if (error) {
    setStatus('error');
    setError(error.message);
    return;
  }

  // Redirect to the URL configured in your Forminit form settings
  window.location.href = redirectUrl;
}
```

If the redirect target is a route inside your own app, use your router for a client-side navigation instead (e.g. React Router's `useNavigate`):

```tsx
import { useNavigate } from 'react-router-dom';

const navigate = useNavigate();

// ...after a successful submit:
navigate(redirectUrl);
```

---

## Available Form Blocks

For complete documentation on all available blocks, field naming conventions, and validation rules, see the [Form Blocks Reference](https://forminit.com/docs/form-blocks/).

---

## Error Handling

Handle common errors appropriately:

```tsx
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
  e.preventDefault();
  setStatus('loading');
  setError(null);

  const formData = new FormData(e.currentTarget);
  const { data, error } = await forminit.submit(FORM_ID, formData);

  if (error) {
    setStatus('error');

    switch (error.error) {
      case 'FI_SCHEMA_FORMAT_EMAIL':
        setError('Please enter a valid email address.');
        break;
      case 'FI_RULES_PHONE_INVALID':
        setError('Please enter a valid phone number (e.g., +12025550123).');
        break;
      case 'FI_SCHEMA_RANGE_RATING':
        setError('Rating must be between 1 and 5.');
        break;
      case 'TOO_MANY_REQUESTS':
        setError('Please wait a moment before submitting again.');
        break;
      default:
        setError(error.message);
    }
    return;
  }

  setStatus('success');
  e.currentTarget.reset();
}
```

### Common Error Codes

| Error Code | Description |
|------------|-------------|
| `FORM_NOT_FOUND` | Form ID doesn't exist or was deleted |
| `FORM_DISABLED` | Form is disabled by owner |
| `EMPTY_SUBMISSION` | No fields with values submitted |
| `FI_SCHEMA_FORMAT_EMAIL` | Invalid email format |
| `FI_RULES_PHONE_INVALID` | Invalid phone number format |
| `FI_SCHEMA_RANGE_RATING` | Rating not between 1-5 |
| `FI_DATA_COUNTRY_INVALID` | Invalid country code |
| `TOO_MANY_REQUESTS` | Rate limit exceeded |

---

## Security Best Practices

1. **Validate input client-side** — Provide immediate feedback to users
2. **Don't rely solely on client validation** — Forminit validates server-side automatically
3. **Restrict submissions with authorized domains** — Limit which domains can submit to your form in Form Settings
4. **Add spam protection** — Enable Cloudflare Turnstile, reCAPTCHA, hCaptcha, or honeypot for public forms
5. **Use HTTPS** — Always use secure connections in production

---

## Related Documentation

- [Form Blocks Reference](https://forminit.com/docs/form-blocks/) — Complete reference for all block types
- [File Uploads](https://forminit.com/docs/file-upload/) — Detailed file upload guide
- [API Reference](https://forminit.com/docs/submit-form-api/) — Full REST API documentation
- [Next.js Integration](https://forminit.com/docs/nextjs/) — Next.js specific setup
- [Node.js Integration](https://forminit.com/docs/nodejs/) — Server-side Node.js setup
- [Cloudflare Turnstile Integration](https://forminit.com/docs/cloudflare-turnstile/) — Add Turnstile protection
- [reCAPTCHA Integration](https://forminit.com/docs/recaptcha/) — Add reCAPTCHA protection
- [hCaptcha Integration](https://forminit.com/docs/hcaptcha/) — Add hCaptcha protection
- [Honeypot](https://forminit.com/docs/honeypot/) — Add honeypot spam protection
