Function Component React Form Submission on Netlify
Tags: javascript, react, netlify
Published: 2021-03-15 19:33:45

I recently built my first version of my [portfolio](alexgamil.com) and deployed it on Netlify. I included a simple contact form for anyone who wants to reach me. In this blog, I will share how I used the built-in form handling that comes with deploying through Netlify. 

I’m going to start this blog assuming that you already have a react-app created and you already ran (npm start or yarn start) and build my form from scratch. You’ll also need to make a Netlify account using the Git provider that is hosting your source code. It is free and it gives you 100 form submissions/month.

I think that’s enough for the intro, let’s get to coding! 

### Step 1 - Create a Form Function

I will be creating 3 input fields. One for `name`, `email`, and `message`. This is my starter code. 

```Javascript
import React from 'react'
const Form = () => {

return (
<div >
  <h1> Sample Form </h1>
   <form >
     <div className="form-inputs">
        <label htmlFor="name" className="form-label">
          Name
        </label>     
        <input 
           type="text" 
           name="name" 
           id="name" 
           className="form-input"/> 
     </div>
     <div className="form-inputs">
        <label htmlFor="email" className="form-label">
          Email
        </label>     
        <input 
           type="text" 
           name="email" 
           id="email" 
           className="form-input"/> 
     </div>
     <div className="form-inputs">
        <label htmlFor="message" className="form-label">
          Message
        </label>     
        <textarea  
           name="message" 
           id="message" 
           className="form-input"/> 
     </div>
     <button type="submit" className="form-input-btn">
       Send
     </button>
   </form>
<div>
    )
}
export default Form
```
I have a `div` that’s holding my `form`. Each `input` is wrapped inside its own `div`. Make sure the `htmlFor` attribute on the `label` and the `name` attribute on the `input` match. For message, we are using `textarea` for multi-line text input. And of course, we have a `button` with the `type=”submit”`

### Step 2 - Stateful Function Component 

We want to make this form stateful so that we could validate the form for errors before submitting it. We don’t want an inbox full of blank messages. We need to import `useState()` hook from `react`.

If you’re not familiar with the `useState()`, it allows us to create a “state variable” in a function component and it returns the `current state` and a `function` that updates that specific state. More info [here](https://reactjs.org/docs/hooks-state.html)

```Javascript
import {useState} from 'react'
```
We would then call the `useState()` like so:

```Javascript
const [formData, setFormData] = useState({
        name: "",
        email: "",
        message: ""
    })
```
We are declaring a “state variable” called `formData`. The argument to the `useState()` is the initial state of our variable. In this case, our initial state is an object with the keys matching our input names, each pointing to a value of an empty string. `setFormData` will be the `function` that will update formData.

We can now add two attributes to our inputs:
> - value={formData.[key in the obj that matches the name]}
> - an onChange event={handleChange}

```Javascript
 <input 
  type="text" 
  name="name" 
  id="name" 
  className="form-input" 
  value={formData.name} 
  onChange={handleChange}/>  
                
 <input 
  type="email" 
  name="email" 
  id="email" 
  className="form-input" 
  value={formData.email} 
  onChange={handleChange}/>
                
 <textarea  
  name="message" 
  id="message" 
  className="form-input" 
  value={formData.message} 
  onChange={handleChange} />
```
And here is the code for the `handleChange()`

```Javascript
const handleChange = e => {
  const { name, value } = e.target
  setFormData({...formData, 
            [name]: value
        })
    }
```
As you can see, we’re destructuring `e.target` and assigning the `name` and `value` to variables and then were calling `setFormData()` to update the state using those variables.

And now we have a stateful react function component! Go ahead and `console.log(formData)` anywhere in the function before return and type away in your inputs.

### Step 3 Creating Validations

If you didn’t code your form from scratch and you got your form from sites like react-bootstrap, you might not have to do this. Some forms might come with form validations already. Anyway moving on!

Were going to call `useState` again and declare a variable called `errors` with the initial state of an empty object. And I also wrote a function called `validate`

```Javascript
 const [errors, setErrors] = useState({})

 const validate = (formData) => {
        let formErrors = {}
        if(!formData.name){
            formErrors.name = "Name required"
        }
        if(!formData.email){
            formErrors.email = "Email required"
        } 
        if(!formData.message){
            formErrors.message = "Message is required"
        }
        return formErrors
    }
```
This function takes in our `formData` variable as a parameter. Then we declare another empty object inside called, `formErrors`. We then have conditions that check if each `key` in `formData` is pointing to an empty string. If it is, this means that our input field is empty then we would add a key-value pair to our `formErrors` object. The `key` is the `name` of the input and the `value` is the exact “error message”. I hope that makes sense lol. Validate returns our `formErrors` object.

Also under each input field, we are putting this code.

```Javascript
<input 
  type="text" 
  name="name" 
  id="name" 
  className="form-input" 
  value={formData.name} 
  onChange={handleChange}/>
{errors.name && <p>{errors.name}</p>}  
                
 <input 
  type="email" 
  name="email" 
  id="email" 
  className="form-input" 
  value={formData.email} 
  onChange={handleChange}/>
{errors.email && <p>{errors.email}</p>}
                
 <textarea  
  name="message" 
  id="message" 
  className="form-input" 
  value={formData.message} 
  onChange={handleChange} />
 {errors.message && <p>{errors.message}</p>}
```

The `&&` is a shortcut that if `errors.name` exists then return this `p` tag, which will be rendering our error message.

Now that we have this setup, I’ll explain what this is for in the next step.

### Step 4.1 Handle Submit

This time we’ll declare a variable called `isSubmitted` with the initial state set to `false`.

```Javascript
const [isSubmitted, setIsSubmitted] = useState(false)

const handleSubmit = e => {
  setErrors(validate(formData))
  setIsSubmitted(true)
  e.preventDefault();
  }
```

As you can see from above, I also built our `handleSubmit` function. 
The function does three things:
> - it will call the `setErrors()` and pass in the return value of the `validate` function which will take `formData` as a parameter
> - we will also set `isSubmitted` to `true`
> - we are preventing default 

Let's go ahead add the onSubmit event to our form

```Javascript
<form onSubmit={handleSubmit}>
```

#### Step 4.2 Handle Submit (useEffect)

Next we are importing the `useEffect` hook from react

```Javascript
import {useState, useEffect} from 'react'
```
If you are not familiar with the `useEffect()`, it is basically equivalent to `componentDidMount()` and `componentDidUpdate()` combined together (and in some cases where it’s needed `componentWillUnmount()`) 

We will call `useEffect` to do our POST request.

```Javascript
useEffect(() => {
 if(Object.keys(errors).length === 0 && isSubmitted){
  fetch("/", {
   method: "POST",
   headers: { "Content-Type": "application/x-www-form-urlencoded" },
   body: encode({ "form-name": "contact-form", ...formData })
   })
   .then(() => alert("Success!"))
   .then(() => setIsSubmitted(false))
   .then(() => setFormData({name: "", email: "",  message: ""}))
   .catch(error => alert(error))}
    }, [errors, formData, isSubmitted])
```

From the code above, we passed a function to `useEffect()`, this function could be referred to as the “effect”. Inside that function is a condition to check if the `errors` object is empty AND `isSubmmitted` set to true. If it is, do the fetch request.

You probably noticed the `body: encode({})` - this is a function provided from the Netlify docs. 

Above the `useEffect()`, copy and paste this exact code.

```Javascript
const encode = (data) => {
 return Object.keys(data)
   .map(key => encodeURIComponent(key) + "=" + 
   encodeURIComponent(data[key]))
   .join("&");
```

What could vary here is the `“contact-form”` and the `...formData.` I will tackle the `“contact-form”` in a bit. For the `formData`, all we’re doing here is using the `spread operator`. We are then passing it to the `encode` function and the return value will be the `body` of the POST request.

This is followed by a “Success” `alert`, setting back `isSubmitted` to `false`, and clearing out the input fields by setting the `formData` to empty strings.

`useEffect()` will apply the effect (which is the function passed) every render. In our case, we want it to skip applying effect unless the `errors` obj and `isSubmitted` state were changed. Basically when someone presses the submit button because `handleSubmit` is where these variables change.

This is why we are passing an array as our second argument to `useEffect()`. When our component re-renders, `useEffect()` compares those variables to the previous variables from the last rerender. If the variables match it will skip the effect, if they don’t then React will run the effect. Essentially in class components, we would write a `componentDidUpdate()` and a comparison with `prevProps` or `prevState`. More info on useEffect [here](https://reactjs.org/docs/hooks-effect.html)

### Step 5 Helping the Netlify bots

The Netlify bots that look for the netlify attribute only know how to parse HTML. So to give them a little help we are including this code inside our index.html (This file is inside the public directory, just above the src directory.) I put it just below the opening `<body>` tag

```Javascript
 <body>
    <form name="contact-form" netlify netlify-honeypot="bot-field" hidden>
      <input type="text" name="name" />
      <input type="email" name="email" />
      <textarea name="message"></textarea>
    </form>
```

Remember the `contact-form` from earlier? The form name has to match what was being encoded in our POST request. In our case it is called “contact-form” You can call it something else, but they just have to match. Otherwise, this won’t work. The second part is that all the `names` for the `inputs` and `textareas` also have to match. In case you’re wondering, labels are not required in this case since this is a hidden form. 

To recap, this is my whole code for the form function.

```Javascript
import React from 'react'
import {useState, useEffect} from 'react'
import './form.css'


const Form = () => {

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

    const handleChange = e => {
        const { name, value } = e.target
        setFormData({
            ...formData, 
            [name]: value
        })
    }

    const [errors, setErrors] = useState({})
    const validate = (formData) => {
        let formErrors = {}
        if(!formData.name){
            formErrors.name = "Name required"
        }
        if(!formData.email){
            formErrors.email = "Email required"
        } 
        if(!formData.message){
            formErrors.message = "Message is required"
        }
        return formErrors
    }

    const [isSubmitted, setIsSubmitted] = useState(false)

    const handleSubmit = e => {
        setErrors(validate(formData))
        setIsSubmitted(true)
        e.preventDefault();
        
    }

    const encode = (data) => {
        return Object.keys(data)
            .map(key => encodeURIComponent(key) + "=" + encodeURIComponent(data[key]))
            .join("&");
      }

    useEffect(() => {
        if(Object.keys(errors).length === 0 && isSubmitted){
            
            fetch("/", {
                method: "POST",
                headers: { "Content-Type": "application/x-www-form-urlencoded" },
                body: encode({ "form-name": "contact-form", ...formData })
            })
            .then(() => alert("Success!"))
            .then(() => setIsSubmitted(false))
            .then(() => setFormData({name: "", email: "",  message: ""}))
            .catch(error => alert(error))
        }
    }, [errors, formData, isSubmitted])
     
    // console.log(errors, formData)
    return (
        <div >
            <h1> Sample Form </h1>
            <form onSubmit={handleSubmit}>
                <div className="form-inputs">
                    <label htmlFor="name" className="form-label">
                        Name
                    </label>     
                    <input 
                        type="text" 
                        name="name" 
                        id="name" 
                        className="form-input" 
                        value={formData.name} 
                        onChange={handleChange}/>
                    {errors.name && <p>{errors.name}</p>}
                </div>
                <div className="form-inputs">
                    <label htmlFor="email" className="form-label">
                        Email
                    </label>     
                    <input 
                        type="email" 
                        name="email" 
                        id="email" 
                        className="form-input" 
                        value={formData.email} 
                        onChange={handleChange}/>
                    {errors.email && <p>{errors.email}</p>}
                </div>
                <div className="form-inputs">
                    <label htmlFor="message" className="form-label">
                        Message
                    </label>     
                    <textarea  
                        name="message" 
                        id="message" 
                        className="form-input" 
                        value={formData.message} onChange={handleChange} />
                    {errors.message && <p>{errors.message}</p>}
                </div>
                <button type="submit" className="form-input-btn">
                    Send
                </button>
            </form>
        </div>
        
    )
}

export default Form

```

This is my optional CSS I added. Not anything fancy!
```Javascript
// form.css

  .form-inputs p {
    font-size: 0.8rem;
    margin-top: 0.5rem;
    color: #f00e0e;
  }

  .form-label {
    display: inline-block;
    font-size: 0.9rem;
    margin-bottom: 6px;
  }
```

### Step 6 Deploy on Netlify

Let's `git add .` `git commit -m "form done"` and `git push`

Below is a 1 minute video of how I deployed this simple form app on Netlify.

{% youtube qaFhcJjyP48 %}

Know that there are tons of other ways to go about this, there are sites like Email.js and Pageclip.co that get the job done. I decided to do it through Netlify because I'm already deploying my portfolio there and I wanted it all in one spot. It was also my first time using Netlify and I love how user-friendly it is. I hope this blog was helpful!

Here is the link to the docs [Netlify](https://www.netlify.com/blog/2017/07/20/how-to-integrate-netlifys-form-handling-in-a-react-app/?_ga=2.172984224.240412651.1615351041-1122630809.1614059232)