React Contact Form

Without Backend, Sent via Email (or Slack)

Forms Example

This guide will show you how to make a nice-looking and elegant form with React. Contact form creation in React shouldn't be a tedious task if you're working on a static/jamstack site.

Often, different guides suggest using backend NodeJS/PHP servers for storing/emailing data. But it's not an absolute rule if you think smart, and your codebase can be a lot cleaner with the fronted-only code.

Using NextJS/TypeScript? Check out the NextJS form guide.

Create the React app

(if you're starting a brand new project)

In case you're starting a brand new project, you'll need some initial steps. One of the most straightforward ways is to use the create-react-app package. To start:

  • Open the terminal and install the create-react-app package
    npm install create-react-app --global
  • Go to the directory where you will store your project. For example
    mkdir ~/react-project && cd ~/react-project
  • Then create your app in this folder; this will be your project root folder
    npx create-react-app .
  • When the installation has finished, you can start the server
    npm start

Use your favorite code editor to work with files in ~/react-project/src. You will be able to make a contact form there.

Add the `herotofu-react` package

To effortlessly handle form submissions, you can use the herotofu-react package. It will do all the form submission process work for you.

npm install --save herotofu-react

Create the contact form component

Create a new file called ContactForm.js in the src folder. You can use any fields and any framework for styling your code. For now, we're staying with the standard "Name," "Email," and "Message" for the simple contact form. We're also going to use TailwindCSS to make it beautiful, but you can use your own custom CSS code too.

import { useFormData } from 'herotofu-react';

const ContactForm = () => {
  // TODO - update to the correct endpoint
  const { formState, getFormSubmitHandler } = useFormData('https://herotofu.com/start');

  return (
    <>
      {!!formState.status && <div className="py-2">Current form status is: {formState.status}</div>}
      <form onSubmit={getFormSubmitHandler()}>
        <div className="pt-0 mb-3">
          <input
            type="text"
            placeholder="Your name"
            name="name"
            className="focus:outline-none focus:ring relative w-full px-3 py-3 text-sm text-gray-600 placeholder-gray-400 bg-white border-0 rounded shadow outline-none"
            required
          />
        </div>
        <div className="pt-0 mb-3">
          <input
            type="email"
            placeholder="Email"
            name="email"
            className="focus:outline-none focus:ring relative w-full px-3 py-3 text-sm text-gray-600 placeholder-gray-400 bg-white border-0 rounded shadow outline-none"
            required
          />
        </div>
        <div className="pt-0 mb-3">
          <textarea
            placeholder="Your message"
            name="message"
            className="focus:outline-none focus:ring relative w-full px-3 py-3 text-sm text-gray-600 placeholder-gray-400 bg-white border-0 rounded shadow outline-none"
            required
          />
        </div>
        <div className="pt-0 mb-3">
          <button
            className="active:bg-blue-600 hover:shadow-lg focus:outline-none px-6 py-3 mb-1 mr-1 text-sm font-bold text-white uppercase transition-all duration-150 ease-linear bg-blue-500 rounded shadow outline-none"
            type="submit"
          >
            Send a message (simple)
          </button>
        </div>
      </form>
    </>
  );
};

export default ContactForm;

Embed contact form into your app, enable styling

Open App.js in your src folder, add contact form component, and enable TailwindCSS. If it's an existing project, open the file where the contact form should appear. You need to:

  1. Import ContactForm — line number #4
  2. Add TailwindCSS (the example is for demo purposes only, for the production please refer to the TailwindCSS installation) — line number #9
  3. Display ContactForm — line number #24
import logo from "./logo.svg";
import "./App.css";
import { useEffect } from "react";
import ContactForm from "./ContactForm";

function App() {
  // You can skip useEffect if you're not using TailwindCSS
  // Otherwise, for the production usage refer to https://tailwindcss.com/docs/installation
  useEffect(() => {
    if (document) {
      const stylesheet = document.createElement("link");
      stylesheet.rel = "stylesheet";
      stylesheet.href = "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css";

      document.head.appendChild(stylesheet);
    }
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div className="py-6">
          <ContactForm />
        </div>
      </header>
    </div>
  );
}

export default App;

Create the free HeroTofu forms endpoint

Head over to herotofu.com and create an account. It will handle all the boring and complex form submission process work for you. You'll get 14 days of the free trial at first, and later you can leave it with the free forever plan. For the sole purpose of the contact form, it's usually more than enough.

HeroTofu registration is straightforward. Fill in the basic fields and then confirm your email address.

Herotofu signup

Once you have confirmed your email address, go to app.herotofu.com/forms to create your first form. Fill in the form name and add your preferred email address where you'd like to receive your form submits. Slack and Zapier are also options, but you need to pay for them once the trial is over.

You'll get the form endpoint URL once you hit submit, so remember to copy it.

Herotofu Forms List

Use the created forms backend in your contact form

Once again, open the ContactForm.js file and fill in the form endpoint URL. You need to change the passed string to the useFormData() at the top of the component. It should look like this.

import { useFormData } from 'herotofu-react';

const ContactForm = () => {
  const { formState, getFormSubmitHandler } = useFormData('EXAMPLE_FORM_ID');

  // The rest of the code...

Done! Go ahead and test your contact form submission! You don't need to do any backend email work, as HeroTofu will handle everything.

Bonus: advanced React implementation
(with dynamic data, callback, and nicer states)

As you can see, the implementation is basic, and you only get those fields on the contact form that are visible on the HTML. That might not work if you need a more customized flow. You'll need to adjust the getFormSubmitHandler handler function to inject extra data dynamically. Good examples could be the user id, the selected plan, or some meta-information of site usage. Here's how it could look like in practice.

import { useFormData } from 'herotofu-react';

const ContactForm = () => {
  // TODO - update to the correct endpoint
  const { formState, getFormSubmitHandler } = useFormData('https://herotofu.com/start');

  // Any data you want to inject into the form submission
  // The data can be defined in the component props or any other place
  const injectedData = {
    userId: '1234',
    userName: 'Example User',
  };

  const onSubmitCallback = ({ status, data }) => {
    // Sync the success/error status with 3rd party services easily. For example, analytics or CRM
    console.log(`The form finished submission in status: ${status} and data: ${JSON.stringify(data)}`);
  };

  const statusBg =
    formState.status === 'not_initialized'
      ? ''
      : formState.status === 'error'
        ? 'bg-red-600 text-red-100'
        : formState.status === 'loading'
          ? 'bg-yellow-600 text-yellow-100'
          : 'bg-green-600 text-green-100';

  return (
    <>
      {!!formState.status && (
        <div className={`mb-4 py-2 ${statusBg}`}>
          Current form status is: {formState.status}
          {formState.status === 'error' && <> {formState.error.message}</>}
        </div>
      )}
      <form onSubmit={getFormSubmitHandler(onSubmitCallback, injectedData)}>

      // The rest of the code is the same...

The core idea is that you send a POST request to the FORM_ENDPOINT of your contact form. But it doesn't matter which way you do it. It can be a regular form submit with ajax handler upon form submission or a completely different request that doesn't involve HTML forms at all

Treat it as a REST API because as long as you're sending POST, you'll be good to go. Your react form submission will reach your inbox.