Implementing a feedback form with Next.js
Tags: nextjs, react
Published: 2023-03-07 00:04:30

by [Nwalozie Elijah](https://blog.openreplay.com/authors/nwalozie-elijah)

In this article, we will provide a step-by-step guide to creating a form that can collect feedback from users of your applications and execute the API functions for us to receive them in a Next.js project. Consider the following scenarios as to why this is important:

1.  You are a developer building your portfolio and want to add a Contact Me section where visitors can enter and submit some messages. And you would receive these messages in real-time in your email.
2. You have just built an amazing product and need some users to test it and give you feedback on improving your application.
3. Lastly, You already have an established website and need to add a feature where users can lay complaints about bugs and irregularities. These messages are then received by you (the owner) or an admin of the site and acted on accordingly.

These are only a few of the many scenarios where this is necessary. It is also worth noting that you don’t need to have back-end knowledge of form collection or database management to have this feature. We will use an email client which collects the form data and deliver them as messages in your emails.


## Pre-requisite

To be able to follow this tutorial, it is advised to know/have the following:

- HTML
- CSS
- JavaScript
- Basic knowledge of React
- npm and Nodejs installed on your hardware


## Installation And Overview

### Installation
First, we have to initialize a Next.js project. Enter the following commands in your terminal.

```bash
npx create-next-app@latest feedback-form
```
You will be prompted with an interactive CLI. Since this is a simple project, toggle `No` on all the options and press `Enter`.

![-](https://i.imgur.com/HjUioSV.png)



On completion, you should have a new folder feedback-form with all Next.js starter files and folders created.  Next, enter the command:

```bash
cd feedback-form
```

### Overview of the Folder Structure
Open the `feedback-form` folder in your code editor. It should have a similar folder structure as the one below.
    
![-](https://paper-attachments.dropboxusercontent.com/s_C9AA4DB899BA19ADED657DDC78905D1FAAE0B1C6D28D8E3A419651CF65AA895A_1674193841903_image.png)

a. node_modules: Contains all dependency packages.
b. Pages: This is where all the React components and API functions live.
c. Pages/api: Nextjs being a `FullStack` framework is made possible by this folder. Files created in this folder represent back-end APIs and functions. Files in this folder by default have a server configured with `Nodejs`, which makes it possible to write back-end code here.
d. Public: As the name implies, files in this folder are accessible in the root folder of your deployed application. For example, `<your-site-name.com/favicon.ico`  displays the favicon in the browser.
e. Styles: This folder contains all CSS styles. `.css` as well as `CSS Modules` files.
The other files at the bottom represent general configurations.

### Creating The Form Component

```javascript
// Replace the code in Pages/index.js with this code

import Head from "next/head";

export default function Home() {
  return (
    <>
      <Head>
        <title>Create Feedback Form</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="form-wrapper">
        <form action="/">
          <div className="form-inner">
            <h1>Send Me A Message</h1>
            <input type="text" placeholder="First Name" />
            <input type="text" placeholder="Last Name"  />
            <input type="email" placeholder="Email" />
            <textarea placeholder="Message..." rows="10" ></textarea>
            <button type="submit" href="/">
              Submit
            </button>
          </div>
        </form>
      </div>
    </>
  );
}
```
Code Breakdown
First, we import the `HEAD` component, which we use to add custom content to the `<head/>` of the generated HTML.
Then we return a simple HTML form with classes to style it.


```css
/* Replace the code in styles/globals.css with this code below */

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
html,
body {
  font-size: 14px;
  color: rgb(70, 69, 69);
}
input,
textarea {
  outline: none;
}
input:is(:focus, :focus-within),
textarea:is(:focus, :focus-within) {
  outline: 1px solid #22d3ee;
}
.form-wrapper {
  height: 100vh;
  padding-inline: 20px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: #22d3ee;
}
h1 {
  font-weight: 500;
  margin-bottom: 10px;
  text-align: center;
}
form {
  width: 100%;
  max-width: 600px;
  border-radius: 30px;
  background: #fff;
  margin: 0 auto;
}
.form-inner {
  padding-inline: 40px;
  padding-block: 30px 40px;
}
.form-inner input,
.form-inner textarea {
  display: block;
  width: 100%;
  padding: 15px;
  margin-bottom: 10px;
  border: none;
  border-radius: 20px;
  background: #d0dfe8;
}
.form-inner textarea {
  resize: none;
}
button {
  width: 100%;
  padding: 10px;
  margin-top: 20px;
  border-radius: 20px;
  border: none;
  border-bottom: 4px solid #0e7490;
  background: #0891b2;
  font-size: 16px;
  font-weight: 400;
  color: #fff;
}
button:hover {
  background: #0e7490;
  cursor:pointer;
}
```
    

We added the following styles to make our form look a bit better. To see what the form currently looks like, run the command below in your terminal

```bash
npm run dev
```

Open the link in a browser (usually, it is http://localhost:3000/), and you should see a simple form similar to the one below.

![-](https://paper-attachments.dropboxusercontent.com/s_C9AA4DB899BA19ADED657DDC78905D1FAAE0B1C6D28D8E3A419651CF65AA895A_1674297662820_image.png)


For this article, I have the fields `Name`, `Email`, `Subject`, and `Message`, which suggest a contact form, but depending on the type of information you wish to collect, the form could have different types of inputs. 

## Values Collection and Submission


### Collecting the form values

```javascript
/* Replace the code in Pages/index.js with this code */

import Head from "next/head";
import { useRef } from "react";

export default function Home() {
  const usernameRef = useRef("");
  const subjectRef = useRef("");
  const emailRef = useRef("");
  const messageRef = useRef("");

  async function submitForm(e) {
    e.preventDefault();
    // Getting the values from their useRef hooks
    let username, subject, email, message;
    username = usernameRef.current.value;
    subject = subjectRef.current.value;
    email = emailRef.current.value;
    message = messageRef.current.value;
    //Some form Validation
    if (!username || !subject || !email || !message) {
      alert("Failed: Ensure to fill all form inputs");
      return;
    }
    // Clear the form inputs after submit
    usernameRef.current.value =
      subjectRef.current.value =
      emailRef.current.value =
      messageRef.current.value =
        "";

    // TODO: Send the form  values to an api route

  }
  return (
    <>
      <Head>
        <title>Create Feedback Form</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="form-wrapper">
        <form action="/" onSubmit={submitForm}>
          <div className="form-inner">
            <h1>Send Me A Message</h1>
            <input type="text" placeholder="Name" required ref={usernameRef} />
            <input type="email" placeholder="Email" required ref={emailRef} />
            <input
              type="text"
              placeholder="Subject"
              required
              ref={subjectRef}
            />
            <textarea
              placeholder="Message..."
              rows="10"
              required
              ref={messageRef}
            ></textarea>
            <button type="submit" href="/">
              Submit
            </button>
          </div>
        </form>
      </div>
    </>
  );
}
```

Code Breakdown
First, we use the [useRef()](https://beta.reactjs.org/reference/react/useRef) react hook to save the input values. Unlike the common [useState()](https://beta.reactjs.org/reference/react/useState) hook, this does not trigger the re-rendering of the component and hence tends to be faster for our use case. See this [useRef article](https://blog.openreplay.com/understanding-the-useref-hook/) to learn more about it.

We also added some validation to check that all the values are entered before sending the form to the server route. A more specific validation could be done in your application depending on the values you aim to collect from the user. Also, all the form input fields have been given the required attribute, but that could be different in your use case.

### Sending the form values to an API route
```javascript
// Update pages/index.js
/*
The previous code remains unchanged
*/

// TODO: Send form values to an api route
const formValues = { username, subject, email, message };
let result;
try {
  let data = await fetch("/api/contact", {
    method: "POST",
    body: JSON.stringify(formValues),
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
    },
  });
  result = await data.json();
} catch (error) {
  result = { message: `Failed: ${error.message}` };
}
alert(result.message); // Gives the user some sort of feedback after the form has been processed

/* 
The rest of the code remains unchanged
*/
```
Note: Refer to the previous step to see where to add the code

Code Breakdown
First, we put the values in an object and use the fetch browser API to send the data to the `/api/contact` route. This is the route we will create in the next step. Also, we check for errors and alert the user with a message based on the result of their submission (success or failure).




<h2>Session Replay for Developers</h2> 
<p><em>
Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data
</em></p>
<img alt="OpenReplay" width="768" height="400" src="https://blog.openreplay.com/assets/overview_Z17Qdyg.png" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em>





## Implementing Functions to Receive The Form Values As Email

Create a new `contact.js` file in the  `/pages/api`  folder and insert the code below:
```javascript
// pages/api/contact.js

export default function handler(req, res) {
  if (req.method === "POST") {
    const formValues = req.body;
    try {
      // Function to validate email and send form values goes here
      return res.status(200).json({ message: "Success: Message Sent" });
    } catch (error) {
      return res
        .status(400)
        .json({
          message: "Failed: Something went wrong. Please try again later",
        });
    }
  }
}
```
    

Code Breakdown
First, we check that the correct method is used to access the route. Then, we get the `formValues` object from the request body. Then, we use the `try/catch` block, which returns success or failure depending on the result. In the `try`  block, we will execute the transport functions.


### Validating and Transporting the message to your Email

We must involve an email delivery service in our application to carry out this step. There are a few of them out there. [Postmark](https://postmarkapp.com/), [Sendgrid](https://sendgrid.com/), [Nodemailer](https://nodemailer.com/about/) to name a few. For this tutorial, we will make use of Nodemailer. 
Nodemailer is a package that helps for easy sending and receiving of mails in Nodejs applications. To learn more about this module, see the [official documentation](https://nodemailer.com/usage/). Nodemailer works well with Gmail, so create a Gmail account to follow this part fully. 

First, we install the `nodemailer` package. Run the command below in the root folder of your project:
        
```bash
npm install nodemailer
```

Next, we need to update the API route 
```javascript  
// Replace the code in pages/api/contact.js

import nodemailer from "nodemailer";

// Replace the email and password values with a string containing your email and password
const email = '<your-gmail-goes-here>'
const password = '<your-gmail-password-goes-here>'

const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    user: email,
    pass: password,
  },
});
export default async function handler(req, res) {
  if (req.method === "POST") {
    const formValues = req.body;
    
    // Server-side form validation
    if (
      !formValues.username ||
      !formValues.email ||
      !formValues.subject ||
      !formValues.message
    )
      return res
        .status(400)
        .json({ message: "Failed: Missing Required Values" });
    try {
      // Validate email and send form values to the validated email
      await transporter.sendMail({
        from: email,
        to: email,
        text: `
        Name: ${formValues.username}
        Email: ${formValues.email}
        Message: ${formValues.message}`,
        subject: formValues.subject,
      });
      return res
        .status(200)
        .json({ message: "Success: Message sent" });
    } catch (error) {
      return res.status(400).json({
        message: `Failed: ${error.message}`,
      });
    }
  }
}
```

Note: Ensure to replace the `email` and `password` values with your own

Code Breakdown
First, we import the nodemailer package, and initialize the `email` and `password`. Then, we create a `transport` (as per the nodemailer documentation) to authenticate our email. Then, in the try block, we call a `sendMail` method for the `transport` and pass in the fields needed for it to work.

Now fill in the input fields and click `Submit.`

![-](https://i.imgur.com/7o03TN6.png)

If you tried running the code (with your Gmail account and password), you would notice it doesn’t work yet; instead, it shows the error message in the image above. This is because Gmail has strict security protocol, and there are still a few more steps to make it work
1. Add 2-step verification to your account: Gmail accounts recently have this active, but If yours does not, Follow the [2-step verification](https://support.google.com/accounts/answer/185839?hl=en&co=GENIE.Platform%3DAndroid) documentation to activate it.
2. Create application password: This is a security procedure where google generates a password for you and can be used for only a specific application. This is to keep your account safe if the password gets compromised.
Go to the[ Application Specific](https://security.google.com/settings/security/apppasswords) page to create a new password named `Nodemailer`.


See the screencast below for an example.

![-](https://i.imgur.com/oIZQnk0.png)

![-](https://i.imgur.com/yZpF208.png)


Congratulations!!!. Submitting the form should now show up as a message in your email. 

![-](https://i.imgur.com/udX6fSa.png)
*After submitting the values now*

It is IMPORTANT to note that putting sensitive data like passwords, private keys, etc., in code is highly discouraged for security reasons. We use environment variables to save such values since they will only be evaluated at runtime. Follow the steps below to save your email and password as environment variables:

1. Create a file called `.env.local` in the root of your `feedback-form` folder
    
![.env.local file added](https://paper-attachments.dropboxusercontent.com/s_C9AA4DB899BA19ADED657DDC78905D1FAAE0B1C6D28D8E3A419651CF65AA895A_1674254938209_image.png)

2. Move your email and password to `.env.local.`

```javascript
// .env.local   
EMAIL=<your-gmail-goes-here>
EMAIL_PASSWORD=<your-nodemailer-generated-password-goes-here>
```
![-](https://i.imgur.com/22DQiYN.png)
            

3. Update the API route 
```javascript            
// Update the code in pages/api/contact.js
import nodemailer from "nodemailer";

/* Replace the raw email and password with the process environment values*/
const email = process.env.EMAIL 
const password = process.env.EMAIL_PASSWORD
/* 
Rest of the code remains unchanged
*/
```
The code should still work the same, but as a security procedure, never put sensitive information in the code. The complete code can be found on [GitHub](https://github.com/Complexlity/feedback-form).

###  Additional Information

The `sendMail` function takes an `html` field in its argument. This could be used to format the message received in a better way than how it is currently. It also takes an `attachments` field to send and receive files. See the [nodemailer documentation](https://nodemailer.com/message/) for the complete list of fields.

## Deploying The Application

This application could be deployed on both Netlify and Vercel. Follow the article on [deploying Next.js to production](https://blog.openreplay.com/how-to-deploy-a-nextjs-app-to-production/) step-by-step to host your application on either of the platforms. The article shows deploying from the CLI, but you could also deploy on the [Netlify GUI](https://www.netlify.com/) and  [Vercel GUI](https://vercel.com/)

IMPORTANT: Add the `EMAIL` and `EMAIL_PASSWORD` as environment variables because the one created before only works in local development.


## Conclusion

In this article, we learned how to receive feedback from users of our Next.js site. And how straightforward it is to use the nodemailer module.




[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)