Skip to main content

Client Component

For client-side form handling, use the React library:
'use client';

import { useForm } from '@spike-forms/react';

export function ContactForm() {
  const { state, handleSubmit, reset } = useForm('YOUR_FORM_SLUG');

  if (state.succeeded) {
    return <p>Thanks for your message!</p>;
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <button type="submit" disabled={state.submitting}>
        {state.submitting ? 'Sending...' : 'Send'}
      </button>
    </form>
  );
}

Server Action

Use Next.js Server Actions for server-side submission:
// app/contact/page.tsx
import { submitForm } from './actions';

export default function ContactPage() {
  return (
    <form action={submitForm}>
      <input type="email" name="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <button type="submit">Send</button>
    </form>
  );
}
// app/contact/actions.ts
'use server';

import { redirect } from 'next/navigation';

export async function submitForm(formData: FormData) {
  const response = await fetch('https://spike.ac/api/f/YOUR_FORM_SLUG', {
    method: 'POST',
    body: formData,
  });

  if (response.ok) {
    redirect('/thanks');
  }
  
  throw new Error('Form submission failed');
}

With useFormState

For better UX with Server Actions:
'use client';

import { useFormState, useFormStatus } from 'react-dom';
import { submitForm } from './actions';

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Sending...' : 'Send'}
    </button>
  );
}

export function ContactForm() {
  const [state, formAction] = useFormState(submitForm, { success: false });

  if (state.success) {
    return <p>Thanks for your message!</p>;
  }

  return (
    <form action={formAction}>
      <input type="email" name="email" required />
      <textarea name="message" required />
      <SubmitButton />
      {state.error && <p className="error">{state.error}</p>}
    </form>
  );
}
// actions.ts
'use server';

export async function submitForm(prevState: any, formData: FormData) {
  const response = await fetch('https://spike.ac/api/f/YOUR_FORM_SLUG', {
    method: 'POST',
    body: formData,
    headers: { 'Accept': 'application/json' }
  });

  if (response.ok) {
    return { success: true };
  }
  
  const data = await response.json();
  return { success: false, error: data.error || 'Submission failed' };
}

API Route Proxy

If you need to add custom logic, create an API route:
// app/api/contact/route.ts
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const formData = await request.formData();
  
  // Add custom validation or processing here
  const email = formData.get('email');
  if (!email || !email.toString().includes('@')) {
    return NextResponse.json({ error: 'Invalid email' }, { status: 400 });
  }

  // Forward to Spike
  const response = await fetch('https://spike.ac/api/f/YOUR_FORM_SLUG', {
    method: 'POST',
    body: formData,
    headers: { 'Accept': 'application/json' }
  });

  const data = await response.json();
  return NextResponse.json(data, { status: response.status });
}

Environment Variables

Store your form slug in environment variables:
NEXT_PUBLIC_SPIKE_FORM_SLUG=abc123xyz
const { handleSubmit } = useForm(process.env.NEXT_PUBLIC_SPIKE_FORM_SLUG!);