Home / Panduan / Integrasi Formulir Kontak React (Aplikasi Vite & Satu Halaman)
PANDUAN INTEGRASI · REAKSI & SPA

Integrasi Formulir Kontak React (Aplikasi Vite & Satu Halaman)

Cara menambahkan formulir kontak dan umpan balik ke React Single-Page Applications (SPA) yang dibuat dengan Vite atau Create React App, yang menampilkan pertahanan spam honeypot dan notifikasi kotak masuk instan.

1. Komponen Formulir Kontak React Standar dengan Vite

Komponen React yang ringan ini menggunakan React hooks standar (useState) dan browser Fetch API untuk mengirimkan langsung ke titik akhir publik Lugatu Anda. Tidak diperlukan pustaka formulir eksternal atau server backend.

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. Integrasi dengan React Hook Form dan Formik

Model pengiriman JSON standar Lugatu terintegrasi dengan mudah dengan pustaka validasi formulir seperti React Hook Form, Formik, atau Zod. Teruskan nilai serial langsung ke objek payload kolom.

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. Variabel Lingkungan & Pemisahan Rahasia di React

Di Vite, variabel publik diawali dengan VITE_. Gunakan VITE_LUGATU_FORM_ID untuk ID formulir publik. Jangan pernah mengekspos kunci API server-ke-server Anda (lug_live_...) di dalam kode sumber atau repositori sisi klien.

warning

Kunci API langsung yang dimulai dengan lug_live_ hanya diperuntukkan bagi eksekusi server-ke-server. Permintaan browser apa pun yang dikirim dengan kunci API langsung ditolak dengan HTTP 403.

Keamanan & Otentikasi Invarian

Invarian Keamanan: Jangan pernah mengawali kunci API langsung dengan VITE_ atau REACT_APP_. Klien browser harus secara eksklusif mengirimkan ke titik akhir publik tanpa kunci API.

Pemecahan Masalah & Kesalahan Penyerapan Umum

Pengiriman formulir mengembalikan kesalahan CORS di konsol browser

Pastikan Anda mengirimkan ke titik akhir publik (https://www.lugatu.com/api/v1/public/forms/YOUR_PUBLIC_FORM_ID/submissions). Titik akhir server-ke-server (POST /api/v1/forms/{id}/submissions) secara eksplisit memblokir asal browser.

Pertanyaan yang Sering Diajukan

Bisakah saya menggunakan React Hook Form atau Formik dengan Lugatu?

Ya. Lugatu menerima JSON standar dengan objek bidang. Pustaka formulir React mana pun dapat membuat serial nilainya dan mengirimkannya ke titik akhir publik.

Siap membuat titik akhir formulir Anda?

Mulailah secara gratis dalam hitungan detik. Tidak diperlukan kartu kredit.

Mulai Gratis →