# Gatsby

> A Gatsby form that already works in the static HTML before hydration, enhanced with fetch after, plus a Gatsby Function for posts with a secret key.

Gatsby renders every page to HTML at build time. Give the form a real `action` and it posts before React hydrates, or if the bundle fails to load; once hydrated, the component posts with `fetch` and shows the result in place.

## Post from the browser

### Step 1: Add the form id

`.env.production`:

```bash
GATSBY_IKIFORM_FORM_ID=frm_xxxxxxxxxxxxxxxx
```

Gatsby only exposes variables that start with `GATSBY_` to browser code, and it inlines them at build time. Add the same line to `.env.development` for `gatsby develop`.

### Step 2: Add the component

`src/components/contact-form.js`:

```jsx
import * as React from "react";

const endpoint = `https://ikiform.com/f/${process.env.GATSBY_IKIFORM_FORM_ID}`;

export default function ContactForm() {
  const [status, setStatus] = React.useState("idle");
  const [message, setMessage] = React.useState("");

  async function onSubmit(event) {
    event.preventDefault();
    setStatus("sending");
    try {
      const response = await fetch(endpoint, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(event.currentTarget),
      });
      const result = await response.json();
      if (result.ok) return setStatus("sent");
      setMessage(result.error.message);
    } catch {
      setMessage("Couldn't reach the server. Try again.");
    }
    setStatus("error");
  }

  if (status === "sent") return <p>Thanks, we got it.</p>;

  return (
    <form action={endpoint} method="post" onSubmit={onSubmit}>
      <label>
        Email
        <input name="email" type="email" autoComplete="email" required />
      </label>
      <label>
        Message
        <textarea name="message" rows={5} />
      </label>
      <input type="hidden" name="_redirect" value="https://example.com/thanks/" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {status === "error" ? <p role="alert">{message}</p> : null}
      <button disabled={status === "sending"}>Send</button>
    </form>
  );
}
```

Render it from any page, for example `src/pages/contact.js`, and add `src/pages/thanks.js` for the no-JavaScript redirect. For field errors and the SDK's human check, extend it as in the [React guide](https://ikiform.com/docs/guides/react.md#component).

## Post from a Gatsby Function

A Gatsby Function can forward the post with a secret key, which skips allowed domains and the human check. It only runs on hosts that support Gatsby Functions, such as Netlify with Gatsby's adapter; a purely static host like GitHub Pages can't run it. Store `IKIFORM_FORM_ID` and a secret key with the `submissions:write` scope as `IKIFORM_SECRET_KEY`, without the `GATSBY_` prefix so they never reach the browser.

`src/api/contact.js`:

```js
export default async function handler(req, res) {
  if (req.method !== "POST") return res.status(405).end();
  const response = await fetch(`https://ikiform.com/f/${process.env.IKIFORM_FORM_ID}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
    },
    body: JSON.stringify(req.body),
  });
  res.status(response.status).json(await response.json());
}
```

Gatsby parses JSON and form-encoded bodies into `req.body`, so the page can post to `/api/contact` either way. All posts then share your function's IP address for rate limits; see [From a server](https://ikiform.com/docs/guides/server.md).

## Allowed domains

This applies to browser posts. Posts from the Gatsby Function use a secret key and skip the check.

| Where the form runs | Add to Allowed domains |
| --- | --- |
| `gatsby develop` and `gatsby serve` | `localhost`. It matches `http://localhost:8000` and `http://localhost:9000`: Ikiform compares the host name only, never the port. |
| Your domain | `example.com`, which also covers `www.example.com` |
| Netlify | `your-site.netlify.app`. Deploy previews (`deploy-preview-12--your-site.netlify.app`) and branch deploys (`branch--your-site.netlify.app`) are not subdomains of it, so add the exact preview host while you test one. |
| Cloudflare Pages | `your-project.pages.dev`. Preview URLs such as `abc123.your-project.pages.dev` are subdomains of it, so they already match. |

- Never add `netlify.app` or `pages.dev` on its own. Every site on that host would match, including other people's.

## Test checklist

1. Run `gatsby build` and open `public/contact/index.html`. The form's `action` should end in your form id, not `undefined`.
2. Run `gatsby serve` with `localhost` in allowed domains and submit. The thanks message should replace the form.
3. Turn JavaScript off, reload and submit. You should see Ikiform's thank-you page locally, or `/thanks/` on your `https:` domain.
4. If you use the function, post to `/api/contact` on a deploy preview and confirm the submission appears in Ikiform.

## Troubleshooting

| What you see | Why | Fix |
| --- | --- | --- |
| `404 not_found`, and the action ends in `/f/undefined` | The variable isn't prefixed with `GATSBY_`, or wasn't set at build time | Name it `GATSBY_IKIFORM_FORM_ID` and rebuild. |
| The function answers `403 forbidden` | The secret key is from another workspace, revoked, or missing `submissions:write` | Create a secret key with that scope. |
| `/api/contact` returns `404` after deploying | The host serves static files only | Deploy to a host with Gatsby Functions, or use the browser version. |
| Submissions arrive under the spam filter with `origin_mismatch` | The page that posted isn't on the form's allowed domains | Add the host from the table above. With **Reject** chosen, the same posts get `403 origin_not_allowed` instead. |

## Related

- [React](https://ikiform.com/docs/guides/react.md): Pending, error and success states.
- [From a server](https://ikiform.com/docs/guides/server.md): Post with a secret API key.
- [Allowed domains](https://ikiform.com/docs/spam.md#allowed-domains): How origins are matched, and what a mismatch does.
- [Errors](https://ikiform.com/docs/errors.md): Every error code, with its cause and fix.
