Home / Guides / React Contact Form Integration (Vite & Single-Page Apps)
INTEGRATION GUIDE · REACT & SPA

React Contact Form Integration (Vite & Single-Page Apps)

How to add contact and feedback forms to React Single-Page Applications (SPA) built with Vite or Create React App, featuring honeypot spam defense and instant inbox notifications.

1. Standard React Contact Form Component with Vite

This lightweight React component uses standard React hooks (useState) and the browser Fetch API to submit directly to your Lugatu public endpoint. No external form libraries or backend servers required.

src/components/ContactForm.tsx tsx
import React, { useState } from 'react';

export function ContactForm() {
  const [formData, setFormData] = useState({ name: '', email: '', message: '', _gotcha: '' });
  const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
  const [errorMessage, setErrorMessage] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (formData._gotcha) return; // Silent discard for automated bot traps

    setStatus('submitting');
    setErrorMessage('');

    try {
      const response = await fetch('https://www.lugatu.com/api/v1/public/forms/YOUR_PUBLIC_FORM_ID/submissions', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
        body: JSON.stringify({
          fields: {
            name: formData.name,
            email: formData.email,
            message: formData.message,
          },
          honeypot: formData._gotcha,
        }),
      });

      if (!response.ok) {
        throw new Error('Failed to deliver message');
      }

      setStatus('success');
      setFormData({ name: '', email: '', message: '', _gotcha: '' });
    } catch (err: any) {
      setStatus('error');
      setErrorMessage(err.message || 'Submission error. Please try again.');
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4 max-w-md">
      <div>
        <label htmlFor="react-name" className="block text-sm font-semibold mb-1">Your Name</label>
        <input
          id="react-name"
          type="text"
          required
          value={formData.name}
          onChange={(e) => setFormData({ ...formData, name: e.target.value })}
          className="w-full border rounded-xl p-2.5"
        />
      </div>

      <div>
        <label htmlFor="react-email" className="block text-sm font-semibold mb-1">Email Address</label>
        <input
          id="react-email"
          type="email"
          required
          value={formData.email}
          onChange={(e) => setFormData({ ...formData, email: e.target.value })}
          className="w-full border rounded-xl p-2.5"
        />
      </div>

      <div>
        <label htmlFor="react-message" className="block text-sm font-semibold mb-1">Message</label>
        <textarea
          id="react-message"
          rows={4}
          required
          value={formData.message}
          onChange={(e) => setFormData({ ...formData, message: e.target.value })}
          className="w-full border rounded-xl p-2.5"
        />
      </div>

      <!-- Honeypot spam defense -->
      <input
        type="text"
        name="_gotcha"
        value={formData._gotcha}
        onChange={(e) => setFormData({ ...formData, _gotcha: e.target.value })}
        style={{ display: 'none' }}
        tabIndex={-1}
        autoComplete="off"
      />

      <button
        type="submit"
        disabled={status === 'submitting'}
        className="btn-primary-editorial py-2.5 px-6"
      >
        {status === 'submitting' ? 'Sending...' : 'Send Message'}
      </button>

      {status === 'success' && (
        <p className="text-sm text-green-700 font-medium">Thank you! Your message was delivered.</p>
      )}
      {status === 'error' && (
        <p className="text-sm text-red-600">{errorMessage}</p>
      )}
    </form>
  );
}

2. Integration with React Hook Form and Formik

Lugatu standard JSON submission model integrates effortlessly with form validation libraries like React Hook Form, Formik, or Zod. Pass the serialized values directly into the fields payload object.

useLugatuForm.ts typescript
// React Hook Form submission handler
const onSubmit = async (data: FormValues) => {
  const res = await fetch(`https://www.lugatu.com/api/v1/public/forms/${import.meta.env.VITE_LUGATU_FORM_ID}/submissions`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ fields: data }),
  });
  return res.ok;
};

3. Environment Variables & Secret Separation in React

In Vite, public variables are prefixed with VITE_. Use VITE_LUGATU_FORM_ID for the public form ID. Never expose your server-to-server API key (lug_live_...) inside client-side source code or repositories.

warning

Live API keys starting with lug_live_ are strictly reserved for server-to-server execution. Any browser request sent with a live API key is rejected with HTTP 403.

Security & Authentication Invariant

Security Invariant: Never prefix live API keys with VITE_ or REACT_APP_. Browser clients must exclusively submit to public endpoints without API keys.

Troubleshooting & Common Ingestion Errors

Form submissions return CORS error in browser console

Ensure you are submitting to the public endpoint (https://www.lugatu.com/api/v1/public/forms/YOUR_PUBLIC_FORM_ID/submissions). Server-to-server endpoints (POST /api/v1/forms/{id}/submissions) explicitly block browser origins.

Frequently Asked Questions

Can I use React Hook Form or Formik with Lugatu?

Yes. Lugatu accepts standard JSON with a fields object. Any React form library can serialize its values and submit to the public endpoint.

Ready to create your form endpoint?

Get started for free in seconds. No credit card required.

Start Free →