send.webforms.toRead the docs →

Frameworks and libraries

Add a contact form to a React app

React has no opinion about where form data goes, which is why every React tutorial stops at onSubmit. WebForms picks it up from there: POST the form to an endpoint and submissions get stored, spam filtered and routed to email, Slack, a webhook or all three. The examples below cover the uncontrolled FormData approach, controlled state and react-hook-form.

Get an endpointRead the docs

Setup

  1. Grab an endpoint

    Create a form in the dashboard and copy its endpoint. No npm install required - this is one fetch call.

  2. Submit the form

    The simplest version passes new FormData(e.currentTarget) straight through, so you never write a change handler per field.

  3. Render a success state

    Ask for JSON with an accept header and you get { ok: true, id } back, so you can swap in a thank-you message without a page reload.

React examples

Swap in your own endpoint and these run as-is.

Uncontrolled - no state per field, the DOM already holds the values.

ContactForm.jsx
import { useState } from "react";

export function ContactForm() {
  const [status, setStatus] = useState("idle");
  const [error, setError] = useState(null);

  async function onSubmit(e) {
    e.preventDefault();
    setStatus("sending");

    const res = await fetch("https://send.webforms.to/wft_your_key", {
      method: "POST",
      headers: { accept: "application/json" },
      body: new FormData(e.currentTarget),
    });

    const data = await res.json();

    if (data.ok) {
      setStatus("sent");
    } else {
      setError(data.message ?? "Something went wrong.");
      setStatus("error");
    }
  }

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

  return (
    <form onSubmit={onSubmit}>
      <input name="name" required />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button disabled={status === "sending"}>Send</button>
      {error && <p role="alert">{error}</p>}
    </form>
  );
}

react-hook-form, posting JSON instead of FormData.

ContactForm.tsx
import { useForm } from "react-hook-form";

type Values = { name: string; email: string; message: string };

export function ContactForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting, isSubmitSuccessful },
  } = useForm<Values>();

  async function onSubmit(values: Values) {
    const res = await fetch("https://send.webforms.to/wft_your_key", {
      method: "POST",
      headers: {
        "content-type": "application/json",
        accept: "application/json",
      },
      body: JSON.stringify(values),
    });
    if (!res.ok) throw new Error("Submission failed");
  }

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("name", { required: "Name is required" })} />
      {errors.name && <span role="alert">{errors.name.message}</span>}

      <input type="email" {...register("email", { required: true })} />
      <textarea {...register("message", { required: true })} />

      <button disabled={isSubmitting}>Send</button>
    </form>
  );
}

Worth knowing

React form FAQ

No. The browser posts directly to your endpoint. That is the whole integration - one fetch call, no backend to deploy or maintain.

Ready to wire up your React form?

Free for 300 submissions a month. Endpoint in under a minute, no card required.

Start free