7. Handling Forms

circle-info

Follow along with code examples herearrow-up-right!

Clone down the repo, cd into it and run npm i to install dependencies.

Table of Contents:

Key Concepts

  • Default form behavior - collect input data, refresh the current page, and send the data to the URL specified in the action attribute using the HTTP GET method (or POST if specified)

  • FormData API — the modern approach to extracting all form values as an object

  • Form validation — checking user input before processing the form data

  • Form reset — clearing all form inputs after successful submission

Key Syntax

  • event.preventDefault() — prevents default form submission behavior

  • form.elements.fieldName.value — access input values by name using form.elements

  • form.elements.checkboxName.checked — access checkbox checked state (returns true/false)

  • new FormData(form) — creates a FormData object from a form element

  • Object.fromEntries(formData) — converts FormData to a plain JavaScript object

  • form.reset() — clears all form inputs to their default values

Forms Review: What We Learned in Module 3

In Module 3, we learned how to build forms using HTML and CSS:

  • How to structure forms with <form>, <label>, <input>, and <button> elements

  • Different input types: text, number, email, date, checkbox, radio, <textarea>, <select>

  • Connecting labels to inputs using for and id attributes for accessibility

  • The name attribute on inputs (which we'll use extensively in this lesson!)

  • HTML validation attributes: required, min, max, minlength, maxlength, pattern

We also learned how to capture the form data and send it to Formspree using the action and method attributes:

Recall that after submitting the form you would be redirected to the Formspree.

Why Handle Forms with JavaScript?

Using services like Formspree is great for simple contact forms, but it has limitations:

  • You lose control — the data is sent away and you can't do anything with it in your app

  • The page reloads/redirects — this breaks the user experience in modern single-page applications

  • No custom validation — you can only use basic HTML validation

  • No dynamic behavior — you can't update the page based on the submitted data

Modern web applications handle forms with JavaScript instead, which allows us to:

  • Keep users on the same page — no reload or redirect

  • Do whatever we want with the data — display it, store it, send it to an API

  • Provide instant feedback — show success messages, validation errors, loading states

  • Create dynamic experiences — add items to a todo list, submit reviews, create posts

Let's start with a simple form with a single field and a button. Take a look at 0-contact-form/index.html:

chevron-rightQ: Take a look at the textarea element. What is the difference between id and name?hashtag
  • The id attribute labels the field so that it can be connected to the <label> element

  • The name attribute gives the form value a name when it is submitted. Remember this!

circle-info

Remember, we are using the Vite development server. To get the app running on the development server:

  • cd into the directory

  • npm i to install Vite dependencies (we did this earlier at the repo root)

  • npm run dev to start the server

  • ctrl+c to stop the server

Handling Forms With JavaScript

Instead of sending the data to another source like Formspree, let's capture the form submission and handle it ourselves.

We'll display a brief status message like "Message Received!" in the empty h3 element and the message in the empty span element

The form's submission is displayed along with a status.

To do this, we'll need to:

  1. Prevent the default page reload/redirect behavior

  2. Extract the form data from the inputs using the name attribute

  3. Use the data (display it, send it to an API, etc.)

  4. Reset the form

Let's look closer at the key parts of the JavaScript:

chevron-rightcontactForm.addEventListener('submit', (event) => {})hashtag
  • The "submit" event is fired when the user presses the submit button.

  • The event handler should use the event parameter for preventing the default behavior.

chevron-rightevent.preventDefault()hashtag
  • event.preventDefault() stops the browser from doing its default action (reload/redirect)

  • It must be called at the start of the handler. Otherwise the page will reload and your JavaScript won't run!

  • Try removing it to see for yourself!

chevron-rightcontactForm.elements.message.valuehashtag
  • contactForm.elements is an object containing all inputs in the form.

  • Inside of it, you can access inputs by their name attribute (e.g., form.elements.message).

  • Then, use .value to get the current value of the input (e.g. form.elements.message.value)

chevron-rightStatus Messagehashtag
  • When handling form submissions, it is a good practice to let your user know if the form submission worked!

  • In this example we always display a success message but you can also show error messages if things like API calls fail

chevron-rightform.reset()hashtag
  • Clears all inputs back to their default values

  • Useful after successful submission

Challenge: Adding A New Input

Let's add name and email inputs to this contact form and display them alongside the message, like this:

A name and email are displayed alongside the message.

HTML

  • Add two form inputs to the html, one for the name and one for the email

    • make sure to give them name attributes!

  • add an output element in the div to display the message (use a <p> with a <span> inside).

    • The format should be "From: Ada Lovelace (ada@mail.com)".

JavaScript

  • Grab the output element.

  • In the event handler, extract the values for the name and the email.

  • Display the formatted message in the output element.

A solution can be found in the 0-contact-forms-solution/ folder.

Handling Checkbox Inputs

Let's add a checkbox to our contact form. Suppose we want to give users the option to send the message anonymously.

HTML:

Checkboxes work differently from text inputs. Every form input has a .value property, but for checkboxes:

  • The .value is always the string "on" whether or not the box is actually checked.

  • The .checked property returns true if the box is checked or false if not.

So, here is the full JavaScript that uses the checkbox to display the user as "Anonymous" if they so choose:

Tip: You can reduce repetition of formElement.elements by destructuring:

FormData API

The FormData API is a more modern approach to extracting data from a form. It automatically extracts ALL form values into a single FormData object. Since it is not an ordinary object, we need to convert it first using Object.fromEntries():

Let's break this down:

new FormData(form)

  • Creates a FormData object containing all the form's input values

  • Automatically finds all inputs with a name attribute

  • The FormData object is iterable but not directly usable as a normal object. Instead, it has "entries".

Object.fromEntries(formData)

  • Converts the FormData object into a plain JavaScript object

  • Each input's name becomes a property

  • Each input's value becomes the property value

Often, the FormData and Object.fromEntries calls are combined into one.

Pros:

  • ✅ Concise — just two lines to get all form data

  • ✅ Automatic — grabs all inputs without listing them

  • ✅ Less code to maintain when adding/removing fields

Cons:

  • ❌ Less explicit — harder to see which fields exist

  • ❌ Checkbox gotcha (see below)

FormData Checkboxes

Remember how we said checkbox .value is always "on"? That's exactly what FormData gives you:

And if the checkbox is unchecked, it won't be included at all:

For basic conditionals and ternary operations, this works fine because of JavaScript's truthiness:

However, the most common use case for the FormData is when packaging the entire object to be sent to an API (which we'll see shortly).

Most APIs will prefer receiving a booleans rather than a value that could either be "on"/undefined. So, we often will reassign a checkbox value after extracting it with FormData:

If you have multiple checkboxes, we need to convert each one:

Sending FormData to APIs

Now let's combine what we learned about forms with what we learned about fetch!

Remember, in Module 3 we used Formspree to handle our form submissions. We set the action attribute to the Formspree URL and the form data was sent automatically when submitted. But this caused the page to redirect.

Now, we can use JavaScript to send the data ourselves using fetch with a POST request — keeping the user on the same page:

Let's break down the key parts:

The fetch configuration object:

  • method: 'POST' — tells the server we're creating/sending new data

  • body: JSON.stringify(formValues) — converts our JavaScript object to a JSON string

  • headers: { 'Content-Type': 'application/json', 'accept': 'application/json' } — tells the server we're sending JSON data and are accepting JSON in response

Why use JSON.stringify()?

The body of a fetch request must be a string, not a JavaScript object. JSON.stringify() converts our object into a JSON-formatted string that can be sent over the network:

Handling the response:

Even though we're sending data (not requesting it), the API still sends back a response. We check response.ok to see if it succeeded, then update the UI accordingly.

Challenge: Build Your Own Form with Formspree!

Test your skills by building your own form with Formspree from scratch! We've given you some code to start with in 1-form-challenge/ but it will be up to you to:

  1. Create a new Formspree form

    1. Go to formspree.ioarrow-up-right and create a free account

    2. Create a new form and copy the endpoint URL (looks like https://formspree.io/f/xyzabc123)

  2. Create a form with inputs. It is up to you what data you want to collect but your form should have:

    • At least one text input field

    • At least one checkbox field

    • A submit button

    • A name attribute for every input

    • A label for every input

  3. Use the FormData approach to extract the form values. Remember to update the checkbox inputs!

  4. Submit the form data to the Formspree URL using a POST request and fetch()

  5. Render a success or an error message

  6. Don't worry about styling. Just aim for functionality!

Use the example in 0-contact-form-solution-with-post as a guide for what this looks like when completed!

Additional Reading

Other Form Events

Besides the submit event, there are several other useful form events:

input event — fires every time an input's value changes (as you type)

change event — fires when an input's value changes AND the input loses focus

focus and blur events — fires when an input gains or loses focus

Non-submit buttons — buttons with type="button" don't submit the form

This is useful for buttons that manipulate form data without submitting!

Form Validation with JavaScript

HTML validation attributes (required, min, max, etc.) are great, but sometimes we need custom validation logic. JavaScript gives us complete control!

Let's build a registration form with custom validation:

Check out this example in 2-registration-form:

HTML: In the HTML, pay attention to the <span class="error" id="input-name-error"></span> elements that have been added for each input.

CSS: Here, we've created a class to make errors stand out.

JavaScript:

In the JavaScript, pay attention to how the helper functions work within the form submission event handler.

This example demonstrates:

  • Custom validation logic (username length, password requirements, matching passwords)

  • Displaying error messages next to the relevant fields

  • Preventing submission if validation fails

  • Clearing errors when validation passes

Challenge: Registration Form with Validation

Enhance the registration form above by adding these validation rules:

  1. Username must:

    • Be at least 3 characters

    • Contain only letters, numbers, and underscores

    • HINT: Use regex /^[a-zA-Z0-9_]+$/

  2. Password must:

    • Be at least 8 characters

    • Contain at least one uppercase letter

    • Contain at least one lowercase letter

    • Contain at least one number

  3. Add visual feedback:

    • Input borders turn red when there's an error

    • Input borders turn green when valid

    • Add CSS classes .error and .valid to inputs

chevron-rightSolutionhashtag

Additional CSS:

Last updated