Skip to main content

Why track form submissions?

Forms are the most critical moments in any app:
  • A signup form = a new user
  • A checkout form = revenue
  • A contact form = a lead
If users are starting your signup form but not finishing it, you’ll only know that by tracking both when the form opens and when it submits successfully.

The form submission pattern

There are two moments you want to track:
  1. When the user starts filling the form (optional but valuable)
  2. When the form submits successfully
components/Forms.js
// Event 1: User opens / focuses the form (optional)
track('form_started', { form: 'signup' })

// Event 2: Form submitted AND your API returned success
track('form_submitted', { form: 'signup' })

React — Signup form example

components/SignupForm.jsx
import { useState } from 'react'
import { track } from '../analytics.jsx'    // Import from your hub!
import { identify } from 'analytiq/react'

export function SignupForm() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  // Track when user starts typing (first interaction)
  function handleFirstFocus() {
    track('signup_form_started')
  }

  // Track successful submission
  async function handleSubmit(e) {
    e.preventDefault()

    try {
      const response = await api.post('/auth/register', { email, password })
      const user = response.data.user

      // Track success AFTER the API call succeeds
      track('signup_completed', { method: 'email' })
      identify(user)   // link future events to this user

      navigate('/dashboard')

    } catch (error) {
      // Track failure too — useful to know how often signups fail
      track('signup_failed', { reason: error.message })
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        onFocus={handleFirstFocus}
        onChange={e => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        onChange={e => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button type="submit">Create Account</button>
    </form>
  )
}

Login form example

components/LoginForm.jsx
import { track } from '../analytics.jsx'
import { identify } from 'analytiq/react'

async function handleLogin(email, password) {
  try {
    const response = await api.post('/auth/login', { email, password })
    const user = response.data.user

    track('login', { method: 'email' })
    identify(user)
    navigate('/dashboard')

  } catch (error) {
    track('login_failed', { reason: 'invalid_credentials' })
  }
}

Contact / enquiry form example

components/ContactForm.jsx
import { track } from '../analytics.jsx'

async function handleContactSubmit(formData) {
  try {
    await api.post('/contact', formData)
    track('contact_form_submitted', {
      subject: formData.subject,
      from_page: window.location.pathname
    })
    showSuccessMessage()

  } catch (error) {
    track('contact_form_failed')
  }
}

Checkout / payment form example

components/CheckoutForm.jsx
import { track } from '../analytics.jsx'

async function handleCheckout(cartData) {
  // Track when they START the checkout
  track('checkout_started', {
    items: cartData.items.length,
    total: cartData.total
  })

  try {
    const response = await api.post('/payments/checkout', cartData)

    // Track successful payment
    track('purchase', {
      amount: cartData.total,
      currency: 'USD',
      items: cartData.items.length
    })

    navigate('/order-confirmation')

  } catch (error) {
    // Track failed payment
    track('checkout_failed', {
      reason: error.message,
      total: cartData.total
    })
  }
}

Key rule: Don’t track the ‘Submit’ button directly!

It is very tempting to just add track() to the final submit button. Do not do this. If you track the button click, your dashboard will show a “Signup” or “Purchase” even if the user’s credit card was declined or they typed the wrong password. You will get fake data. Always add your tracking code after your database or payment system confirms it was successful:
api/users.js
// Wrong — tracks before knowing if it actually worked
track('signup_completed')
await database.saveUser(data)

// Correct — tracks only if the database save was successful
await database.saveUser(data)
track('signup_completed')

EventWhen to fire
signup_form_startedUser focuses the first input field
signup_completedAPI returns success
signup_failedAPI returns error
loginLogin API returns success
login_failedLogin fails
contact_form_submittedContact form sends successfully
checkout_startedUser clicks “Proceed to Checkout”
purchasePayment confirmed
checkout_failedPayment fails