# React forms

A copy-paste React form component that submits to Nisuform with fetch, with spam protection and success and error states.

Collect the fields, `POST` them to your endpoint and check the response. You don't need an API route, a server or environment variables, because the form key is safe to publish. The component works in any React app, like one built with Vite or React Router.

<Callout title="Generated for your form">
  The form's **Setup** tab, under **Code**, has this component ready with your endpoint and field names filled in. **Your design** gives you the styled form from the builder as a component. **Your own styles** gives you a bare component to style your way.
</Callout>

## The component

```jsx title="ContactForm.jsx"
import { useRef, useState } from 'react'

export function ContactForm() {
  const shownAt = useRef(Date.now())
  const [status, setStatus] = useState('idle')

  async function onSubmit(event) {
    event.preventDefault()
    setStatus('sending')
    const data = Object.fromEntries(new FormData(event.currentTarget))
    data._elapsed = String(Date.now() - shownAt.current)
    const response = await fetch('https://api.nisuform.com/s/YOUR_FORM_KEY', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify(data),
    })
    setStatus(response.ok ? 'sent' : 'error')
  }

  if (status === 'sent') {
    return <p>Thanks! We got your message.</p>
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" placeholder="Name" required />
      <input type="email" name="email" placeholder="Email" required />
      <textarea name="message" rows={5} required />
      <input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" style={{ display: 'none' }} />
      <button type="submit" disabled={status === 'sending'}>Send</button>
      {status === 'error' && <p role="alert">Something went wrong. Please try again.</p>}
    </form>
  )
}
```

`Object.fromEntries` keeps only the last value of fields that share a name. For checkbox groups, send the `FormData` itself instead of JSON.

## What it does

* Sends `Accept: application/json`, so Nisuform answers with JSON instead of the thank-you page. See [JavaScript and fetch](/docs/connect/javascript).
* Adds `_elapsed`, the time the form was open, and keeps the hidden `_gotcha` honeypot empty. Both feed [spam protection](/docs/protection/spam). See [Special fields](/docs/connect/special-fields).
* Shows a success message when the submission is accepted, and an error when it isn't.

For a version with React 19's `useActionState`, TypeScript and field errors, read the [React contact form guide](/blog/react-contact-form).
