guides

How to create a Next.js form

Header graphic that shows the Next.js logo

Create your form endpoint in FormBackend

Go create a login and create a new form endpoint in FormBackend. Give it a name you can remember for example: “NextJS Contact Form” or something similar for this tutorial. It can always be changed later and is only used for you to remember your form.

Create a new Next.js app

If you don’t have an existing Next.js app, let’s create one real quick - if you do, then you can skip this section and go to the next one.

Per NextJS own documentation I would check that you have Node.js version 10.13 or later. You can use Node.js own instructions to install it.

Let’s go ahead and create the Next.js app. Go to the directory where you want the app to be created in your terminal. We’re going to use create-next-app via npx to create the app.

npx create-next-app formbackend-nextjs

Let’s cd into the directory that was just created which contains our Next.js app.

cd formbackend-nextjs

We can start the development server with yarn run dev. That will start the Next.js development server, which will automatically reload everytime you make any changes.

We can access our local Next.js app on http://localhost:3000. Visiting that URL should show you the Next.js starter page and a big “Welcome to Next.js!” headline.

Create our contact form page

Go ahead and create a new file named contact.js in the pages directory.

We’ll give it the following content for now:

import React, { useState } from "react"

export default function Contact() {
  return (
    <div>
      <h1>Contact form</h1>
      <form method="POST" action="https://www.formbackend.com/f/664decaabbf1c319">
        <div>
          <label>Name</label>
          <input type="text" name="name" />
        </div>

        <div>
          <label>Email</label>
          <input type="text" name="email" />
        </div>

        <div>
          <label>Message</label>
          <textarea name="message"></textarea>
        </div>

        <button type="submit">Send message</button>
      </form>
    </div>
  )
}

This is a simple form with three form fields: name, email and message. You can access the page if you visit http://localhost:3000/contact. Before doing anything change the action attribute of the form tag, to the URL that matches the form you created in FormBackend (you can find the url by visiting the “Setup”-tab).

Go ahead and visit the contact page you just created. Fill out the form and click the “Send message” button. You’re not presented with a “Thank you for your submission” page and you should be able to see the submission under submissions for your form in FormBackend.

Let us add a bit of javascript to this form

Let’s go ahead and make the experience a little nicer. We’re going to add a bit of JavaScript and change how we handle the form state. Let’s start by adding some new useState variables at the top of your page code just below the export default ... line:

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

One thing to keep in mind here is: We want the name, email and message attributes in the state to match the name tag of each field we added to the form. If we don’t do this it won’t work!

Now we have a place to temporarily store the values that people enter in to your form before we submit it to FormBackend.

We need to trigger a function that does this when people enter data in the respective form fields.

On each of the three form fields, let’s add a onChange callback. Let’s look at the name field as an example:

<input type="text" name="name" onChange="handleInput" />

Make sure you add onChange to the email and message fields as well!

Time to write out our handleInput function:

const handleInput = (e) => {
  const fieldName = e.target.name;
  const fieldValue = e.target.value;

  setFormData((prevState) => ({
    ...prevState,
    [fieldName]: fieldValue
  }));
}

Now every time someone writes something in the form fields, it updates the state representing the form data.

But how do we submit it to FormBackend you’re asking?

We need to add another callback to the form. This time it’s the onSubmit callback:

<form method="POST" action="[your-unique-url]" onSubmit={submitForm}>

Let’s write out the submitForm function:

const submitForm = (e) => {
  // We don't want the page to refresh
  e.preventDefault()

  const formURL = e.target.action
  const data = new FormData()

  // Turn our formData state into data we can use with a form submission
  Object.entries(formData).forEach(([key, value]) => {
    data.append(key, value);
  })

  // POST the data to the URL of the form
  fetch(formURL, {
    method: "POST",
    body: data,
    headers: {
      'accept': 'application/json',
    },
  }).then(() => {
    setFormData({
      name: "",
      email: "",
      message: ""
    })
  })
}

Let us take a closer look at what’s going on in the above code.

The e.preventDefault() line, tells the form to not do it’s default behavior which is to submit and refresh the page.

We then store the value of the action attribute on the form (the FormBackend URL) in the formURL variable.

We need a way to take our formData state and turn that in to a proper FormData object that the browser can use when submitting the form to FormBackend. We do that by generating a new FormData() object and assigning that to the data variable. We then iterate over all the values in our formData state object and append that to the FormData assigned to data.

Now it’s time to submit the form, for that we’re going to use fetch which is in all modern browsers. We set the method to POST as that is what FormBackend expects (and the right way to send data over the wire). We assign our FormData object data to the body of the request and set the accept header to application/json as we want FormBackend to return json and not the full HTML page in the response.

Once the form has been submitting successfully, then() will get run, inside of that we reset our formData state object by setting all the field values to blank. We need to add one small thing for this to work properly. For each of our fields we want to bind the value to their respective value in our formData state object. Like so:

<div>
  <label>Name</label>
  <input type="text" name="name" value={formData.name} />
</div>

<div>
  <label>Email</label>
  <input type="text" name="email" value={formData.email} />
</div>

<div>
  <label>Message</label>
  <textarea name="message" value={formData.message}></textarea>
</div>

Now when we submit the form, we send all the data to FormBackend, we reset our formData values and you’ll see the form be reset to all empty values again.

Show a message once the form has been submitted

It can be a little jarring to submit a form, and see all the fields reset without any confirmation of what just happened. So let’s display a small message on the page and hide the form once it has been submitted successfully.

In order to do so we need to introduce a few extra things in our Next.js contact form. First let’s introduce a new variable named formSuccess using useState. Towards the top of the file add the following:

  const [formSuccess, setFormSuccess] = useState(false)

This new state will keep track of if the Next.js form has been submitted successfully. So inside of the then() callback in our formSubmit function let’s add setFormSuccess(true) right after setFormData(...) so it’ll look like this:

}).then(() => {
  setFormData({
    name: "",
    email: "",
    message: ""
  })

  setFormSuccess(true)
})

We will now add a bit of logic to our HTML by using the conditional (ternary) operator:

return (
  <div>
    <h1>Contact form</h1>
    {formSuccess ?
      <div>Form submitted</div>
      :
      <form method="POST" action="https://www.formbackend.com/f/664decaabbf1c319" onSubmit={submitForm}>
        <div>
          <label>Name</label>
          <input type="text" name="name" onChange={handleInput} value={formData.name} />
        </div>

        <div>
          <label>Email</label>
          <input type="text" name="email" onChange={handleInput} value={formData.email} />
        </div>

        <div>
          <label>Message</label>
          <textarea name="message" onChange={handleInput} value={formData.message}></textarea>
        </div>

        <button type="submit">Send message</button>
      </form>
    }
  </div>
)

Notice towards the top how we check if formSucess is true and if it is we display Form submitted if not, we display the form. That’ll make it so when you submit the form we hide the form itself and show Form submitted.

Use the submission message returned by the server

We can make this even more dynamic and use the submittion text returned by the server. Make the following change around the then() line:

}).then((response) => response.json())
.then((data) => {

We will now parse the server response as JSON which we can access via the data attribute. Let’s introduce a new piece of state which we’ll call formSuccessMessage. Towards the top of the file we’ll add:

const [formSuccessMessage, setFormSuccessMessage] = useState("")

We will then change our “Form submitted” message to <div>{formSuccessMessage} and below our setFormData reset in the then callback for fetch - we’ll add:

setFormSuccessMessage(data.submission_text)

The JSON returned by FormBackend when you submit the Next.js form using JavaScript contains the following:

{
  submission_text: "Thank you for your submission",
  redirect_url: null,
  errors: [],
  values: {
    name: "John Doe",
    email: "hello@formbackend.com",
    message: "My message"
    }
}

Where values is the submitted values from our form and submission_text is the text we have entered on the “Settings” page for your form (default is: Thank you for your submission)

The final result

The final version of our contact.js Next.js page looks like this:

import React, { useState } from "react"

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

  const [formSuccess, setFormSuccess] = useState(false)
  const [formSuccessMessage, setFormSuccessMessage] = useState("")

  const handleInput = (e) => {
    const fieldName = e.target.name;
    const fieldValue = e.target.value;

    setFormData((prevState) => ({
      ...prevState,
      [fieldName]: fieldValue
    }));
  }

  const submitForm = (e) => {
    // We don't want the page to refresh
    e.preventDefault()

    const formURL = e.target.action
    const data = new FormData()

    // Turn our formData state into data we can use with a form submission
    Object.entries(formData).forEach(([key, value]) => {
      data.append(key, value);
    })

    // POST the data to the URL of the form
    fetch(formURL, {
      method: "POST",
      body: data,
      headers: {
        'accept': 'application/json',
      },
    }).then((response) => response.json())
    .then((data) => {
      setFormData({
        name: "",
        email: "",
        message: ""
      })

      setFormSuccess(true)
      setFormSuccessMessage(data.submission_text)
    })
  }

  return (
    <div>
      <h1>Contact form</h1>
      {formSuccess ?
        <div>{formSuccessMessage}</div>
        :
        <form method="POST" action="https://www.formbackend.com/f/664decaabbf1c319" onSubmit={submitForm}>
          <div>
            <label>Name</label>
            <input type="text" name="name" onChange={handleInput} value={formData.name} />
          </div>

          <div>
            <label>Email</label>
            <input type="text" name="email" onChange={handleInput} value={formData.email} />
          </div>

          <div>
            <label>Message</label>
            <textarea name="message" onChange={handleInput} value={formData.message}></textarea>
          </div>

          <button type="submit">Send message</button>
        </form>
      }
    </div>
  )
}

That is how you create a simple contact form for Next.js and gradually make it more advanced using FormBackend. The code can be found on GitHub. Please reach out to us if you have any questions.