Let's build a contact form with Next.js
Tags: nextjs, react, tutorial, webdev
Published: 2022-12-29 12:58:17

Most of the websites have a contact page where you can send a message to reach the owner. They look something like this:

![Contact form on superface.ai includes email, first and last name, company, message, and selection of demo.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgvy8fjt04yugr2c5qcr.png)

In this article, we will create a similar form with React in Next.js. First, I will create a front-end part with the form, and then I will build an API route which will send the form to your email.

## Setting up the application

First, let’s create a new Next.js project. We will create it in a `contact-form` folder, with JavaScript and ESLint enabled:

```bash
npx create-next-app contact-form --js --eslint
```

This will create the folder and installs all the dependencies.

Now enter the folder (`cd contact-form`) and start the development server:

```bash
npm run dev
```

Visit `http://localhost:3000` to check the running application.

## Creating the form

The main file where we are going to make changes is `pages/index.js`. Remove the original code inside the file and paste the following code:

```jsx
import React from 'react';

export default function Home() {
  return (
    <form className="container">
      <h1>Get in touch</h1>
      <div className="email block">
        <label htmlFor="frm-email">Email</label>
        <input
          id="frm-email"
          type="email"
          name="email"
          autoComplete="email"
          required
        />
      </div>
      <div className="block phone">
        <label htmlFor="frm-phone">Phone</label>
        <input
          id="frm-phone"
          type="text"
          name="phone"
          autoComplete="tel"
          required
        />
      </div>
      <div className="name block">
        <div>
          <label htmlFor="frm-first">First Name</label>
          <input
            id="frm-first"
            type="text"
            name="first"
            autoComplete="given-name"
            required
          />
        </div>
        <div>
          <label htmlFor="frm-last">Last Name</label>
          <input
            id="frm-last"
            type="text"
            name="last"
            autoComplete="family-name"
            required
          />
        </div>
      </div>
      <div className="message block">
        <label htmlFor="frm-message">Message</label>
        <textarea id="frm-message" rows="6" name="message"></textarea>
      </div>
      <div className="button block">
        <button type="submit">Submit</button>
      </div>
    </form>
  );
}
```

This code creates a form with the following fields:

- email
- first name
- last name
- phone number
- message

All fields are required except for the message.

To add style to the form, replace the contents of `styles/globals.css` file with the following code:

```css
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  background: #1e1e1e;
  min-height: 100vh;
  display: flex;
  color: rgb(243, 241, 239);
  justify-content: center;
  align-items: center;
}

.block {
  display: flex;
  flex-direction: column;
}

.name {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.container {
  font-size: 1.3rem;
  border-radius: 10px;
  width: 85%;
  padding: 50px;
  box-shadow: 0 54px 55px rgb(78 78 78 / 25%), 0 -12px 30px rgb(78 78 78 / 25%),
    0 4px 6px rgb(78 78 78 / 25%), 0 12px 13px rgb(78 78 78 / 25%),
    0 -3px 5px rgb(78 78 78 / 25%);
}

.container input {
  font-size: 1.2rem;
  margin: 10px 0 10px 0px;
  border-color: rgb(31, 28, 28);
  padding: 10px;
  border-radius: 5px;
  background-color: #e8f0fe;
}

.container textarea {
  margin: 10px 0 10px 0px;
  padding: 5px;
  border-color: rgb(31, 28, 28);
  border-radius: 5px;
  background-color: #e8f0fe;
  font-size: 20px;
}

.container h1 {
  text-align: center;
  font-weight: 600;
}

.name div {
  display: flex;
  flex-direction: column;
}

.block button {
  padding: 10px;
  font-size: 20px;
  width: 30%;
  border: 3px solid black;
  border-radius: 5px;
}

.button {
  display: flex;
  align-items: center;
}

textarea {
  resize: none;
}
```

Our form should look something like this:

![Styled form with Email, Phone, First name, Last name, and Message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wirl3do58c0ah19a2i6n.png)

Now we need to find a way to store the input entered by the user. I will use [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) which is natively supported in all current browsers. It loads fields from the form, so they can be then submitted to the server.

Inside the `pages/index.js` file, paste the following code (notice the new `handleSubmit` function):

```jsx
import React from 'react';

export default function Home() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    console.log(data);
  }

  return (
    <form className="container" onSubmit={handleSubmit}>
      <h1>Get in touch</h1>
      <div className="email block">
        <label htmlFor="frm-email">Email</label>
        <input
          id="frm-email"
          type="email"
          name="email"
          autoComplete="email"
          required
        />
      </div>
      <div className="block phone">
        <label htmlFor="frm-phone">Phone</label>
        <input
          id="frm-phone"
          type="text"
          name="phone"
          autoComplete="tel"
          required
        />
      </div>
      <div className="name block">
        <div>
          <label htmlFor="frm-first">First Name</label>
          <input
            id="frm-first"
            type="text"
            name="first"
            autoComplete="given-name"
            required
          />
        </div>
        <div>
          <label htmlFor="frm-last">Last Name</label>
          <input
            id="frm-last"
            type="text"
            name="last"
            autoComplete="family-name"
            required
          />
        </div>
      </div>
      <div className="message block">
        <label htmlFor="frm-message">Message</label>
        <textarea id="frm-message" rows="6" name="message"></textarea>
      </div>
      <div className="button block">
        <button type="submit">Submit</button>
      </div>
    </form>
  );
}
```

Now when you attempt to submit the form, you should see `FormData` in developer console.

## Submitting the form to API

We will submit the form data to the API with `[fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)` – no need for additional dependencies.

The form submission logic goes to the `handleSubmit` function. Here is the complete code in `pages/index.js`:

```jsx
import React from 'react';

export default function Home() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    try {
      const response = await fetch('/api/contact', {
        method: 'post',
        body: new URLSearchParams(data),
      });
      if (!response.ok) {
        throw new Error(`Invalid response: ${response.status}`);
      }
      alert('Thanks for contacting us, we will get back to you soon!');
    } catch (err) {
      console.error(err);
      alert("We can't submit the form, try again later?");
    }
  }

  return (
    <form className="container" onSubmit={handleSubmit}>
      <h1>Get in touch</h1>
      <div className="email block">
        <label htmlFor="frm-email">Email</label>
        <input
          id="frm-email"
          type="email"
          name="email"
          autoComplete="email"
          required
        />
      </div>
      <div className="block phone">
        <label htmlFor="frm-phone">Phone</label>
        <input
          id="frm-phone"
          type="tel"
          name="phone"
          autoComplete="tel"
          required
        />
      </div>
      <div className="name block">
        <div>
          <label htmlFor="frm-first">First Name</label>
          <input
            id="frm-first"
            type="text"
            name="first"
            autoComplete="given-name"
            required
          />
        </div>
        <div>
          <label htmlFor="frm-last">Last Name</label>
          <input
            id="frm-last"
            type="text"
            name="last"
            autoComplete="family-name"
            required
          />
        </div>
      </div>
      <div className="message block">
        <label htmlFor="frm-message">Message</label>
        <textarea id="frm-message" rows="6" name="message"></textarea>
      </div>
      <div className="button block">
        <button type="submit">Submit</button>
      </div>
    </form>
  );
}
```

The `handleSubmit` sends data inside a `POST` request to the `/api/contact` route. We also wrap the `FormData` object in `URLSearchParams` to send data as `application/x-www-form-urlencoded` which is automatically decoded by Next.js API routes handler.

## Handling form submission in the API route

Now we need to handle the form submission on the server. We are going to use [Next.js API routes](https://nextjs.org/docs/api-routes/introduction) for that. API routes are located in `pages/api` folder. Let’s create `pages/api/contact.js` file which corresponds to the API route `/api/contact`.

First inside the `pages/api/contact.js` file paste the following code to test if we receive the data on the server.

```jsx
export default function handler(req, res) {
  console.log(req.body);
  res.send(200);
}
```

Try submitting the form now, you should see the data logged on the terminal. And now we are getting to the juicy part.

## Sending emails with SendGrid and Superface

When a user submits the contact form, we want to send the submitted information to the website owner. First, we need to pick some email providers and study their API and SDK. Or we can use [Superface](https://superface.ai/) with any provider.

Superface makes API integrations super easy. We don’t have to deal with API docs, and I can use many providers behind the same interface. Furthermore, I can use more ready-made API use cases from the [Superface catalog](https://superface.ai/catalog). It’s a tool worth having in your toolbox.

### Set up SendGrid

I’m going to use [SendGrid](https://sendgrid.com/) as an email provider with Superface. Create your account, [get your API key](https://docs.sendgrid.com/ui/account-and-settings/api-keys#types-of-api-keys) with Full Access and [verify Single Sender Verification](https://docs.sendgrid.com/ui/sending-email/sender-verification).

On Superface side, pick the use case, i.e.: [Send Email](https://superface.ai/communication/send-email).

<figure>

![Superface integrations catalog include other use cases, like Send SMS Message, or Send Templated Message.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q67p8mnoc7he98i7p0f2.png)

</figure>

### Sending emails from the API route

Superface use cases are consumed with OneSDK, so we will have to install it.

```bash
npm i @superfaceai/one-sdk
```

On Superface in [Send Email use case](https://superface.ai/communication/send-email?provider=sendgrid), select `sendgrid` as a provider. We can use most of the code from the example in our API route handler, we just need to pass data correctly from the request.

![Send Email use case with SendGrid selected as a provider](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jgvwy3fqtclo21a18tk0.png)

Paste the following code into your `pages/api/contact.js` file:

```jsx
const { SuperfaceClient } = require('@superfaceai/one-sdk');

const sdk = new SuperfaceClient();

// Just check if all required fields are provided
function formValid(body) {
  return body.email && body.phone && body.first && body.last;
}

export default async function handler(req, res) {
  const body = req.body;

  if (!formValid(body)) {
    res.status(422).end();
    return;
  }

  const profile = await sdk.getProfile('communication/send-email@2.1.0');
  const message = `
    Email: ${body.email}
    Phone: ${body.phone}
    Name: ${body.first} ${body.last}
    Message: ${body.message} 
    `;
  const result = await profile.getUseCase('SendEmail').perform(
    {
      from: process.env.FROM_EMAIL,
      to: process.env.TO_EMAIL,
      subject: 'Message from contact form',
      text: message,
    },
    {
      provider: 'sendgrid',
      security: {
        bearer_token: {
          token: process.env.SENDGRID_API_KEY,
        },
      },
    }
  );

  try {
    const data = result.unwrap();
    console.log(data);
    res.status(201).end();
  } catch (error) {
    console.error(error);
    res.status(500).end();
  }
}
```

You can notice that we are referring to some environment variables in the code (for example `process.env.SENDGRID_TOKEN`). We can store these in [.env files](https://nextjs.org/docs/basic-features/environment-variables). In the root of your project, create a `.env.local` file with the following contents (make sure to edit values):

```bash
# Email address verified in Single Sender Verification
FROM_EMAIL=from@example.com
# Email address where you want to send the submissions to
TO_EMAIL=to@example.com
# Sendgrid API key
SENDGRID_API_KEY=SG.abcdef...
```

Our app is ready. Run (or restart) the development server with `npm run dev` and try to submit the form!

<figure>

![Form filled with all fields filled in](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eo2yyshgz03m6oe9axq4.png)

</figure>

<figure>

![Received email with the form submissions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/smcspse8a05shqsnvtqy.png)

</figure>

## Conclusion

We have learned how to create a form in Next.js, submit the form with `FormData` and fetch, handle the submission in the API route and send it through an email.

Further possible improvements include adding CAPTCHA or honeypot fields to prevent spam submissions, improving form data validation with checks of phone and email address, format the submission as HTML, or providing a nicer feedback to the user upon submissions.

Besides sending an email, we can do a lot more with submissions, like sending data to CRM or Slack, or handle newsletter subscriptions. But that’s for another time – [follow our profile](https://dev.to/superface) or [sign up for our monthly newsletter](https://sfc.is/newsletter), so you don't miss our future tutorials.