Beginner7 min

How to Add a Contact Form to React

Build a fully functional contact form in React with hooks, validation, and email notifications. No backend server or API routes needed.

Prerequisites

  • A React project (Create React App, Vite, or similar)
  • A FormsList account (free)
  • Basic knowledge of React hooks
1

Set up your FormsList endpoint

Create a free FormsList account, add a new form, and copy the endpoint URL. This endpoint accepts POST requests with form data and handles storage, validation, and email delivery on the server side.

2

Build the form component with React hooks

Create a new component that uses useState to track field values and submission state. The form collects name, email, and message fields. Using controlled inputs gives you full control over validation and UX before the data is sent.

import { useState, ChangeEvent, FormEvent } from "react";

interface FormFields {
  name: string;
  email: string;
  message: string;
}

export default function ContactForm() {
  const [fields, setFields] = useState<FormFields>({ name: "", email: "", message: "" });
  const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");

  function handleChange(e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
    setFields({ ...fields, [e.target.name]: e.target.value });
  }

  async function handleSubmit(e: FormEvent) {
    e.preventDefault();
    setStatus("sending");

    try {
      const res = await fetch("https://formslist.com/f/YOUR_FORM_HASH", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(fields),
      });

      if (res.ok) {
        setStatus("sent");
        setFields({ name: "", email: "", message: "" });
      } else {
        setStatus("error");
      }
    } catch {
      setStatus("error");
    }
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: 480 }}>
      <div>
        <label htmlFor="name">Name</label>
        <input id="name" name="name" value={fields.name} onChange={handleChange} required />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" name="email" type="email" value={fields.email} onChange={handleChange} required />
      </div>
      <div>
        <label htmlFor="message">Message</label>
        <textarea id="message" name="message" rows={4} value={fields.message} onChange={handleChange} required />
      </div>
      <button type="submit" disabled={status === "sending"}>
        {status === "sending" ? "Sending..." : "Send Message"}
      </button>
      {status === "sent" && <p style={{ color: "green" }}>Thank you! Your message has been sent.</p>}
      {status === "error" && <p style={{ color: "red" }}>Something went wrong. Please try again.</p>}
    </form>
  );
}
3

Add client-side validation

Enhance the form with inline validation before submission. Check that the email format is valid and the message meets a minimum length. This improves user experience by catching mistakes early, while FormsList still validates on the server side as a safety net.

function validate(fields: FormFields): Record<string, string> {
  const errors: Record<string, string> = {};
  if (fields.name.trim().length < 2) errors.name = "Name must be at least 2 characters.";
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(fields.email)) errors.email = "Enter a valid email address.";
  if (fields.message.trim().length < 10) errors.message = "Message must be at least 10 characters.";
  return errors;
}

// Inside handleSubmit, before sending:
const errors = validate(fields);
if (Object.keys(errors).length > 0) {
  // Display errors to the user
  setFieldErrors(errors);
  setStatus("idle");
  return;
}
4

Render the form and configure notifications

Import and render ContactForm in your app. Submit a test entry to verify everything works end to end. In the FormsList dashboard, enable email forwarding and optionally connect Slack or webhook integrations to receive real-time alerts when someone contacts you.

import ContactForm from "./ContactForm";

function App() {
  return (
    <div className="App">
      <h1>Get in Touch</h1>
      <ContactForm />
    </div>
  );
}

export default App;

Frequently Asked Questions

Ready to collect form submissions?

Set up your form backend in under a minute. No server required, no complex configuration — just a simple endpoint for your forms.