guides

How to add a form to your Gatsby site

Header graphic that shows the Gatsby logo

Create your form endpoint in FormBackend

Go create a login and create a new form endpoint in FormBackend. Give it a name you can remember for example: “Gatsby Contact Form” or something similar for this tutorial. It can always be changed later and is only used for you to remember your form.

Create a new Gatsby app

Let’s start out by creating a new Gatsby site. If you have an existing Gatsby site you can skip to the next section. Open up a terminal and type the following to start creating your Gatsby site:

npm install -g gatsby-cli

After gatsby-cli is installed, let’s go ahead and create your site

gatsby new formbackend-gatsby

You can of course call the site whatever you want, formbackend-gatsby is just for the purpose of this tutorial.

Start the Gatsby server

Go ahead and start the Gatsby server by cd to the Gatsby directory you just created. When in that directory, type the following:

gatsby develop

You can now navigate to the site in your browser by visiting https://localhost:8000

Create your form endpoint in FormBackend

Visit FormBackend and create a new form. Give it a name you can remember for example: “Gatsby Form” or whatever you feel like. It can be anything and is only used so you can find your form later.

Create a new contact page

Let’s create a new contact us page. We do that by creating a new file src/pages/contact.js.

First let’s put some basic boilerplate in it:

import * as React from "react"
import Layout from "../components/layout"
import Seo from "../components/seo"
import * as styles from "../components/index.module.css"

function ContactPage() {

  return (
    <Layout>
      <Seo title="Contact us" />
      <div className={styles.textCenter}>
        <h1>
          Contact us
        </h1>

        <form method="POST" action="https://www.formbackend.com/f/664decaabbf1c319">
            <div>
              <label>Name</label>
              <input type="text" name="name" />
            </div>

            <div>
              <label>Email</label>
              <input type="text" name="email" />
            </div>

            <div>
              <label>Message</label>
              <textarea name="message"></textarea>
            </div>

            <button type="submit">Send message</button>
          </form>

      </div>
    </Layout>
  )
}

export default ContactPage

This gives you an unstyled form with a Name, Email and Message field. Make sure you replace {your-unique-formbackend-url} with the unique URL you get from FormBackend when you created a form. That URL can be found by going to the “Setup” tab for the newly created form.

The form works as is, we can submit it and the submissions go to FormBackend. But we can spice it up a little more and utilize the fact that we have a JacaScript framework.

Submit the form using JavaScript

Let’s make use of JavaScript and submit the form without a page refresh.

First let’s set up some local state, which will keep track of what is entered into the Gatsby form fields.

Towards the top, right after function ContactPage() { add the following:

const [formData, setFormData] = useState({
  name: "",
  email: "",
  message: ""
});

We need to make sure that name, email and message in the above matches the name-attributes of our three form-fields (the two inputs and the textarea).

We need to add an onChange event handler on each of our inputs, so the text that is entered gets captured in the state we just added. Let’s look at the name field as an example:

<input type="text" name="name" onChange="handleInput" />

You of course need to add that to the email and message fields as well.

Now let’s go ahead and write the handleInput function:

const handleInput = (e) => {
  const fieldName = e.target.name;
  const fieldValue = e.target.value;

  setFormData((prevState) => ({
    ...prevState,
    [fieldName]: fieldValue
  }));
}

Now every time you type something in to one of the three form fields, the state object will be updated with the value for the corresponding field. This happens because we take e.target which is the field the value is being entered into. Then we find the name of that field as well as the value and call setFormData where we target the fieldName key and set the value. So if you type in the field with name name - that specific key/value pair will be updated in state.

Submit the form data to FormBackend

Time to look at how to submit the entered data, which is now stored in state to FormBackend. For that we’re going to add a onSubmit callback on the form:

<form method="POST" action="[your-unique-url]" onSubmit={submitForm}>

Let’s write the submitForm function:

const submitForm = (e) => {
  // We don't want the page to refresh
  e.preventDefault()

  const formURL = e.target.action
  const data = new FormData()

  // Turn our formData state into data we can use with a form submission
  Object.entries(formData).forEach(([key, value]) => {
    data.append(key, value);
  })

  // POST the data to the URL of the form
  fetch(formURL, {
    method: "POST",
    body: data,
    headers: {
      'accept': 'application/json',
    },
  }).then(() => {
    setFormData({
      name: "",
      email: "",
      message: ""
    })
  })
}

If everything is hooked up according to the above, if you open the network inspector in your browser you’ll see the data is being submitted to FormBackend. But the values are still in the form after submission? Why is that? We need to bind each field to the value in the state object. We do that by adding a value attribute for each field like so:

<div>
  <label>Name</label>
  <input type="text" name="name" value={formData.name} />
</div>

<div>
  <label>Email</label>
  <input type="text" name="email" value={formData.email} />
</div>

<div>
  <label>Message</label>
  <textarea name="message" value={formData.message}></textarea>
</div>

But what is actually happening in the submitForm function we just wrote? Let’s take a closer look.

When you submit a regular HTML form, the page refreshes. We don’t want that to happen so that’s why we call e.preventDefault() where e is the submission event. We then get the form itself via e.target and get the action attribute off of that.

We need a way to take the submitted data that is stored in the formData state and turn that in to a proper FormData object which the browser uses to submit the data to the server. So we instantiate a new variable data and assign FormData() to that. We then iterate over all the entries in our formData state object and add the key/value pairs to the newly instantiated FormData() object.

To actually submit the form, we use fetch which is supported by all modern browsers. We set the method to POST which is what FormBackend expects (and the correct way to submit new data over the wire). We assign our FormData object data to the body of the request and set the accept header to application/json as we want FormBackend to return json and not the full HTML page in the response.

Once the form has been submitting successfully, then() will get run, inside of that we reset our formData state object by setting all the field values to blank.

Show a message when the form has been submitted

Instead of just having an empty form when it has been submitted, let’s replace it with a message letting the user know that all went well.

In order to do so we need to add a little bit of extra state. So let’s go ahead and add the following:

  const [formSuccess, setFormSuccess] = useState(false)

This newly added state will keep track of if form has been submitted successfully. So inside of the then() callback in our formSubmit function let’s add setFormSuccess(true) right after setFormData(...) so it’ll look like this:

}).then(() => {
  setFormData({
    name: "",
    email: "",
    message: ""
  })

  setFormSuccess(true)
})

We will then add a conditional to our HTML by using a conditionoal (ternary) operator:

return (
  <Layout>
    <Seo title="Contact us" />
    <div className={styles.textCenter}>
      <h1>
        Contact us
      </h1>

      {formSuccess ?
        <div>Form submitted</div>
        :
      <form method="POST" action="https://www.formbackend.com/f/664decaabbf1c319" onSubmit={submitForm}>
          <div>
            <label for="name">Name</label>
            <input type="text" name="name" onChange={handleInput} value={formData.name} />
          </div>

          <div>
            <label for="email">Email</label>
            <input type="text" name="email" onChange={handleInput} value={formData.email} />
          </div>

          <div>
            <label for="message">Message</label>
            <textarea name="message" onChange={handleInput} value={formData.message}></textarea>
          </div>

          <button type="submit">Send message</button>
        </form>
      }
    </div>
  </Layout>
)

Notice how, if formSubmitted is true we render a div with the text “Form submitted”.

Use the thank you message returned by the server

We can make this dynamic and use the submission text returned by FormBackend (this can be changed in the settings for your form). Make the following change around the then() line:

}).then((response) => response.json())
.then((data) => {

We will parse the server response as JSON which we can access via the data attribute. We need to add a new piece of state which we’ll call formSuccessMessage. Towards the top of the file we’ll add:

const [formSuccessMessage, setFormSuccessMessage] = useState("")

We then change our “Form submitted” message to <div>{formSuccessMessage} and below our setFormData reset in the then callback for fetch we add:

setFormSuccessMessage(data.submission_text)
``

The JSON returned by FormBackend when you submit the Gatsby form using JavaScript contains the following:

```javascript
{
  submission_text: "Thank you for your submission",
  redirect_url: null,
  errors: [],
  values: {
    name: "John Doe",
    email: "hello@formbackend.com",
    message: "My message"
    }
}

Where values is the submitted values from our form and submission_text is the text we have entered on the “Settings” page for your form (default is: Thank you for your submission)

The final Gatsby form code

The final version of our code looks like this:

import React, { useState } from "react"
import Layout from "../components/layout"
import Seo from "../components/seo"
import * as styles from "../components/index.module.css"

function ContactPage() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    message: ""
  });

  const [formSuccess, setFormSuccess] = useState(false)
  const [formSuccessMessage, setFormSuccessMessage] = useState("")

  const handleInput = (e) => {
    const fieldName = e.target.name;
    const fieldValue = e.target.value;

    setFormData((prevState) => ({
      ...prevState,
      [fieldName]: fieldValue
    }));
  }

  const submitForm = (e) => {
    // We don't want the page to refresh
    e.preventDefault()

    const formURL = e.target.action
    const data = new FormData()

    // Turn our formData state into data we can use with a form submission
    Object.entries(formData).forEach(([key, value]) => {
      data.append(key, value);
    })

    // POST the data to the URL of the form
    fetch(formURL, {
      method: "POST",
      body: data,
      headers: {
        'accept': 'application/json',
      },
    }).then((response) => response.json())
    .then((data) => {
      setFormData({
        name: "",
        email: "",
        message: ""
      })

      setFormSuccess(true)
      setFormSuccessMessage(data.submission_text)
    })
  }

  return (
    <Layout>
      <Seo title="Contact us" />
      <div className={styles.textCenter}>
        <h1>
          Contact us
        </h1>

        {formSuccess ?
          <div>{formSuccessMessage}</div>
          :
        <form method="POST" action="https://www.formbackend.com/f/664decaabbf1c319" onSubmit={submitForm}>
            <div>
              <label for="name">Name</label>
              <input type="text" name="name" onChange={handleInput} value={formData.name} />
            </div>

            <div>
              <label for="email">Email</label>
              <input type="text" name="email" onChange={handleInput} value={formData.email} />
            </div>

            <div>
              <label for="message">Message</label>
              <textarea name="message" onChange={handleInput} value={formData.message}></textarea>
            </div>

            <button type="submit">Send message</button>
          </form>
        }
      </div>
    </Layout>
  )
}

export default ContactPage

And that is how you create a contact form for your Gatsby site and gradually make it more advanced to use JavaScript to handle submissions and show a small message after it has been submitted.