Skip to main content

Composition API

<script setup lang="ts">
import { ref } from 'vue';

const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle');
const error = ref<string | null>(null);

async function handleSubmit(e: Event) {
  e.preventDefault();
  status.value = 'loading';
  error.value = null;

  const form = e.target as HTMLFormElement;
  const data = new FormData(form);

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

    if (response.ok) {
      status.value = 'success';
      form.reset();
    } else {
      const result = await response.json();
      error.value = result.error || 'Submission failed';
      status.value = 'error';
    }
  } catch {
    error.value = 'Network error';
    status.value = 'error';
  }
}

function reset() {
  status.value = 'idle';
  error.value = null;
}
</script>

<template>
  <div v-if="status === 'success'">
    <p>Thanks for your message!</p>
    <button @click="reset">Send another</button>
  </div>
  
  <form v-else @submit="handleSubmit">
    <input type="email" name="email" placeholder="Email" required />
    <textarea name="message" placeholder="Message" required></textarea>
    <button type="submit" :disabled="status === 'loading'">
      {{ status === 'loading' ? 'Sending...' : 'Send' }}
    </button>
    <p v-if="error" class="error">{{ error }}</p>
  </form>
</template>

Composable

Create a reusable composable:
// composables/useSpike.ts
import { ref } from 'vue';

export function useSpike(formSlug: string) {
  const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle');
  const error = ref<string | null>(null);

  async function submit(data: Record<string, unknown>) {
    status.value = 'loading';
    error.value = null;

    try {
      const response = await fetch(`https://spike.ac/api/f/${formSlug}`, {
        method: 'POST',
        body: JSON.stringify(data),
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        }
      });

      if (response.ok) {
        status.value = 'success';
      } else {
        const result = await response.json();
        error.value = result.error || 'Submission failed';
        status.value = 'error';
      }
    } catch {
      error.value = 'Network error';
      status.value = 'error';
    }
  }

  function reset() {
    status.value = 'idle';
    error.value = null;
  }

  return { status, error, submit, reset };
}
Usage:
<script setup>
import { useSpike } from '@/composables/useSpike';

const { status, error, submit, reset } = useSpike('YOUR_FORM_SLUG');

function handleSubmit(e) {
  e.preventDefault();
  const form = e.target;
  const data = Object.fromEntries(new FormData(form));
  submit(data);
}
</script>

Options API

<script>
export default {
  data() {
    return {
      status: 'idle',
      error: null
    };
  },
  methods: {
    async handleSubmit(e) {
      e.preventDefault();
      this.status = 'loading';
      this.error = null;

      const form = e.target;
      const data = new FormData(form);

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

        if (response.ok) {
          this.status = 'success';
          form.reset();
        } else {
          const result = await response.json();
          this.error = result.error || 'Submission failed';
          this.status = 'error';
        }
      } catch {
        this.error = 'Network error';
        this.status = 'error';
      }
    }
  }
};
</script>

Nuxt 3

Works the same way in Nuxt 3:
<!-- pages/contact.vue -->
<script setup>
const status = ref('idle');

async function handleSubmit(e) {
  status.value = 'loading';
  
  const { data, error } = await useFetch('https://spike.ac/api/f/YOUR_FORM_SLUG', {
    method: 'POST',
    body: Object.fromEntries(new FormData(e.target)),
    headers: { 'Accept': 'application/json' }
  });

  status.value = error.value ? 'error' : 'success';
}
</script>