Complexlity
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Implementing a feedback form on a Next.js site ## Introduction In this article, we will provide a step-by-step guide to creating a form with the ability to collect feedback from users of your applications and also execute the API functions for us to receive them in a NextJs project. Consider the following scenarios as to why this is important: 1. You are a developer building your portfolio. You want to add a Contact Me section where visitors could enter some messages and submit them. 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 out and give you feedback on how to improve your application. 3. Lastly, You already have an established website and you 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 to have. It is also worth noting that you don’t need to have some back-end knowledge of form collection or database management to be able to have this feature. We would make use of 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 would be prompt 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 backend 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 looking 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. To learn more about it, see this [useRef article](https://blog.openreplay.com/understanding-the-useref-hook/). 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 also 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 would create in the next step. Also, we check for errors and alert the user a message based on the result of their submission (success or failure). ## Implementing Functions to Receive The Form Values As Email ### Creating the API route Create a new file `contact.js` 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 result. In the `try` block, we would execute the transport functions. ### Validating and Transporting the message to your Email To carry out this step, we need to involve an email delivery service in our application. 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 would 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 **ensure to 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 enter fill 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 will 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 with the name `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 would 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 code**. The complete code could 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**: Ensure to add the `EMAIL` and `EMAIL_PASSWORD` as environment variables because the one created before only works in local development. ## Conclusion In this article, we learnt how to receive feedback from users of our NextJs site. And how straightforward it is using the nodemailer module.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully