Skip to content

React

Every example on this page is a self-contained React component using fetch - no BootForm SDK to install. Jump to just the one you need from the outline on the right, or read top to bottom for the full picture.

Basic submission

The plain HTML case works unchanged inside JSX - no state needed at all:

jsx
function ContactForm() {
  return (
    <form action="https://f.bootform.com/{form_id}" method="POST">
      <input name="email" type="email" required />
      <textarea name="message" />
      <button type="submit">Send</button>
    </form>
  )
}

AJAX submission

Intercept the submit, post as JSON, and track status with useState instead of navigating away:

jsx
import { useState } from 'react'

function ContactForm() {
  const [status, setStatus] = useState('idle') // 'idle' | 'sending' | 'sent' | 'error'
  const [error, setError] = useState(null)

  async function handleSubmit(event) {
    event.preventDefault()
    setStatus('sending')

    const response = await fetch(event.target.action, {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: new FormData(event.target),
    })
    const result = await response.json()

    if (result.ok) {
      event.target.reset()
      setStatus('sent')
    } else {
      setError(result.error)
      setStatus('error')
    }
  }

  return (
    <form action="https://f.bootform.com/{form_id}" method="POST" onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <textarea name="message" />
      <button type="submit" disabled={status === 'sending'}>
        {status === 'sending' ? 'Sending…' : 'Send'}
      </button>
      {status === 'sent' && <p>Thanks - your message was sent.</p>}
      {status === 'error' && <p role="alert">{error}</p>}
    </form>
  )
}

File upload

FormData picks up a file input the same way as text fields - nothing React-specific needed beyond the state you're already tracking above:

jsx
function UploadForm() {
  const [status, setStatus] = useState('idle')

  async function handleSubmit(event) {
    event.preventDefault()
    setStatus('sending')

    const response = await fetch(event.target.action, {
      method: 'POST',
      headers: { Accept: 'application/json' },
      body: new FormData(event.target), // includes the selected file automatically
    })
    const result = await response.json()
    setStatus(result.ok ? 'sent' : 'error')
  }

  return (
    <form action="https://f.bootform.com/{form_id}" method="POST" onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <input name="resume" type="file" required />
      <button type="submit" disabled={status === 'sending'}>Send</button>
    </form>
  )
}

A file upload requires the form to be claimed first - 422 FORM_NOT_CLAIMED otherwise. See Building your form.

Moderated content feed

Load a form's approved submissions on mount, and let visitors react - the feed endpoint is public and unauthenticated, no API key involved:

jsx
import { useEffect, useState } from 'react'

function ContentFeed({ formId }) {
  const [items, setItems] = useState([])

  async function loadFeed() {
    const response = await fetch(`https://f.bootform.com/forms/${formId}/feed?per_page=20`)
    const { items } = await response.json()
    setItems(items)
  }

  useEffect(() => {
    loadFeed()
  }, [formId])

  async function react(submissionId, type) {
    await fetch(`https://f.bootform.com/forms/${formId}/feed/${submissionId}/react`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ type }),
    })
    loadFeed() // refetch to show the updated count
  }

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          <p>{item.fields.message}</p>
          <button onClick={() => react(item.id, 'up')}>👍 {item.reactions.up}</button>
          <button onClick={() => react(item.id, 'down')}>👎 {item.reactions.down}</button>
        </li>
      ))}
    </ul>
  )
}

See Moderated content feed for the report endpoint, pagination, and the hosted-widget alternative if you'd rather not write this yourself.