Skip to Content
 August 25, 2019
 2 Minutes
 Computer Science
 GatsbyJS, React, Netlify

I've really enjoyed working with Gatsby and Netlify over the past few weeks, but I struggled to find good information regarding how to do AJAX form submissions with these 2 technologies and I mostly had to go through a process of trial and error. So, I'm going to hopefully remedy that.

Form Setup

Here's a basic React/Netlify form that we'll be working with:

<form 
    name="contact-form"
    id="contact-form"
    method="POST"
    data-netlify="true" 
    data-netlify-honeypot="bot-field" 
>
    <input type="hidden" name="bot-field" />
    <input type="hidden" name="form-name" value="contact-form" />
    <div>
        <label htmlFor="name">Name</label>
        <input 
            type="text" 
            name="name" 
            value={formData.name}
            onChange={e => handleChange(e)}
      	/>
    </div>
    <div>
        <label htmlFor="email">Email</label>
        <input 
            type="text" 
            name="email" 
            value={formData.email}
            onChange={e => handleChange(e)}
      	/>
    </div>
    <div>
        <label htmlFor="message">Message</label>
        <textarea 
            name="message" 
            value={formData.message}
            onChange={e => handleChange(e)}
       	/>
    </div>
    <div>
        <button 
            type="submit" 
            name="submit" 
        >
            Send Email
        </button>
    </div>
</form>

where formData and handleChange are:

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

const handleChange = event => {
    const key = event.target.name;
    const updatedFormValue = event.target.value;
    const newFormData = {...formData, [key]: updatedValue};
    setFormData(newFormData);
};

In this example formData uses React Hook, useState and handleChange just updates our formData for a specific field every time a user types in one of our form fields.

When the form in its current state is submitted the user will be brought to the page in src/pages/success.js of your Gatsby project or Netlify's default page if one does not exist. We instead want to stay on our current page and maybe provide some information to the user that the submission did or didn't go through.

The Solution

To have this behavior we need to add some attributes to our form:

<form 
    name="contact-form"
    id="contact-form"
    method="POST"
    onSubmit={e => handleSubmit(e)} //ADDED
    action={THIS_PAGE} //ADDED
    data-netlify="true" 
    data-netlify-honeypot="bot-field" 
>

The action attribute must be set to the target of the AJAX request. THIS_PAGE is just a string that represents your current URL, my form is at the base URL and I want to stay there so THIS_PAGE would be '/' for me. The handleSubmit function would look something like this:

const handleSubmit = event => {
    event.preventDefault();
    
    const form = event.target;
    fetch(THIS_PAGE, {
        method: 'POST',
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams({
            "form-name": form.getAttribute('name'),
            ...formData
        })
        	.toString() 
    })
        .then(response => { //where your custom handling goes
            if (!response.ok)
                throw Error(response.statusText);

            const emptyForm = createEmptyForm(formData);
            setFormData(emptyForm);

            setStatusText("Thank you!");
        })
        .catch(error => setStatusText(`Error: ${error.message}`);
};

The body of the fetch POST request must be an object that contains a key "form-name" with the respective form name as the value (in this example it would be "contact-form"). This is necessary so Netlify knows which form it's getting data from. Your form name and data must also be encoded with something like URLSearchParams.

new URLSearchParams({
    "form-name": "contact-form",
    name: "Anonymous", 
    email: "aperson@somewhere.com",
    message: "your site sucks", 
})
	.toString(); 
//"form-name=contact-form&name=Anonymous&email=aperson%40somewhere.com&message=your+site+sucks"

Inside the then, after the fetch promise resolves is probably where you want the logic to re-render your component. This code is ran after your form is submitted to Netlify, so some basic things that would make sense to do here are checking the response status code to make sure it's valid, setting all the form fields to be empty strings, or setting a flag which renders a thank you or error message.

That's it, here's the full code for a basic Gatsby/Netlify contact form:

import React, { useState } from 'react';

const THIS_PAGE = "/";

const ContactForm = () => {
    const [formData, setFormData] = useState({
        name: "", 
        email: "",
        message: "", 
    });
    const [statusText, setStatusText] = useState("");
    
    const handleChange = event => {
        const key = event.target.name;
        const updatedFormValue = event.target.value;
        const newFormData = {...formData, [key]: updatedValue};
        setFormData(newFormData);
	};
    
    const handleSubmit = event => {
        event.preventDefault();
        
        const form = event.target;
        fetch(THIS_PAGE, {
            method: 'POST',
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: new URLSearchParams({
                "form-name": form.getAttribute('name'),
                ...formData
            })
            	.toString();
        })
            .then(response => {
                if (!response.ok)
                    throw Error(response.statusText);

                const emptyForm = createEmptyForm(formData);
                setFormData(emptyForm);

                setStatusText("Thank you!");
            })
            .catch(error => setStatusText(`Error: ${error.message}`);
    };
    
    return (
        <>
        	<p>{statusText}</p>
            <form 
                name="contact-form"
                id="contact-form"
                method="POST"
            	onSubmit={e => handleSubmit(e)}
    			action={THIS_PAGE}
                data-netlify="true" 
                data-netlify-honeypot="bot-field" 
            >
                <input type="hidden" name="bot-field" />
                <input type="hidden" name="form-name" value="contact-form" />
                <div>
                    <label htmlFor="name">Name</label>
                    <input 
                        type="text" 
                        name="name" 
                        value={formData.name}
                        onChange={e => handleChange(e)}
                    />
                </div>
                <div>
                    <label htmlFor="email">Email</label>
                    <input 
                        type="text" 
                        name="email" 
                        value={formData.email}
                        onChange={e => handleChange(e)}
                    />
                </div>
                <div>
                    <label htmlFor="message">Message</label>
                    <textarea 
                        name="message" 
                        value={formData.message}
                        onChange={e => handleChange(e)}
                    />
                </div>
                <div>
                    <button 
                        type="submit" 
                        name="submit" 
                    >
                        Send Email
                    </button>
                </div>
            </form>
		</>
    );
};

export default ContactForm;

Like what you see?
Have any advice or questions?
Contact Me