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.

Where React submissions can go

The guide above gets the submission to your endpoint. From there it can land anywhere: one destination on Free, several on Pro.

Need the form markup too? The template gallery has complete forms for contact, waitlist, job application and more, and the form generator builds one field by field.

Ready to wire up your React form?

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

Start free