Form Handling
Intercept form submission with the submit event and preventDefault, read submitted values with FormData and individual field references, run basic validation, display errors, and reset the form.
By default, submitting an HTML form reloads the entire page (or navigates to a new one) and sends its data as a plain HTTP request. Modern JavaScript-driven sites almost always intercept that submission instead, so they can validate the data, send it to a server in the background with fetch, and update the page without a jarring full reload.
The submit event and preventDefault
The submit event fires on the <form> element itself, not on any individual button, whenever the form is about to be submitted — whether that was triggered by clicking a <button type="submit"> or by pressing Enter inside a text field.
Calling event.preventDefault() inside the submit handler cancels the browser's default behavior (the page navigation/reload), leaving JavaScript fully in control of what happens with the form's data next — validating it, sending it with fetch, or simply logging it.
Reading form data with FormData
The FormData API collects the current value of every named form control in one step: new FormData(formElement) returns an object keyed by each control's name attribute, without you having to manually query each <input> one at a time.
FormData is especially convenient because it also works correctly with file inputs and can be sent directly as the body of a fetch() request without any manual serialization.
Reading individual field values
Sometimes you only need one or two specific fields, and querying them directly is simpler: document.querySelector("#email").value returns the current text of that input. For checkboxes, use .checked (a boolean) rather than .value; for a group of radio buttons, you typically need to find the one where .checked is true.
Basic validation and error display
Once you have the values, plain JavaScript conditionals can check them: is a required field empty, does a password meet a minimum length, do two password fields match? When a check fails, the usual pattern is to show a specific error message next to that field — commonly by setting the textContent of a small, initially empty element reserved for that purpose — rather than relying only on a generic alert().
Resetting the form
Calling formElement.reset() restores every field in the form to its initial value (the value it had when the page loaded, or its default), exactly as if the user had clicked a <button type="reset">. This is commonly done after a successful submission, so the form is ready for the next entry.
Intercepting a form submission
event.preventDefault() must be called synchronously, before any async work, to reliably cancel the default submission. data.get("fieldName") reads a single value by the matching input's name attribute. Use data.getAll("fieldName") for a field that can have multiple values, like a multi-select or a group of same-named checkboxes.
form.addEventListener("submit", function (event) {
event.preventDefault();
const data = new FormData(form);
const email = data.get("email");
// ...validate and use the data...
});Code Examples
This is the minimal skeleton every form handler starts with: intercept the submit event and stop the browser's default page reload before doing anything else. Expected result: Form submission intercepted, page will not reload event.preventDefault() is what stops the full-page reload — without it, everything after this line would still run, but the page would navigate away immediately afterward.
new FormData(form) reads the current value of every field named in the markup (username, email, newsletter) in one call. A checkbox's value is the string "on" when checked and absent entirely when unchecked, so comparing to "on" converts it to a real boolean. Expected result: Collected values: { username: "jane_dev", email: "[email protected]", newsletter: true } data.get("newsletter") === "on" converts a checkbox's raw form value into a proper true/false.
Every submission first clears any previous error message, then re-checks the field. If validation fails, an error is shown next to the field, focus is returned to it, and the function returns early so nothing is actually submitted. Only a passing check reaches form.reset(). Expected result: (with an empty email) email-error now reads: "Email is required." (with a valid email) Submitting: [email protected] — then the form fields are cleared return; after showing the error stops execution before the (would-be) real submission logic runs.
const form = document.querySelector("#contact-form");
form.addEventListener("submit", function (event) {
event.preventDefault();
console.log("Form submission intercepted, page will not reload");
});const form = document.querySelector("#signup-form");
form.addEventListener("submit", function (event) {
event.preventDefault();
const data = new FormData(form);
const values = {
username: data.get("username"),
email: data.get("email"),
newsletter: data.get("newsletter") === "on"
};
console.log("Collected values:", values);
});const form = document.querySelector("#signup-form");
const emailInput = document.querySelector("#email");
const emailError = document.querySelector("#email-error");
form.addEventListener("submit", function (event) {
event.preventDefault();
emailError.textContent = "";
const email = emailInput.value.trim();
if (email === "") {
emailError.textContent = "Email is required.";
emailInput.focus();
return;
}
console.log("Submitting:", email);
form.reset();
});Important Rules
The submit event fires on the <form>, not on the submit button.
event.preventDefault() inside the submit handler stops the browser's default page reload/navigation.
new FormData(form) collects every named field's current value in one step; data.get("name") reads one.
form.reset() restores all fields to their initial values, the same as a reset button would.
Where this shows up
Every login form, signup form, checkout form, and search box on the modern web intercepts submit, validates, and sends data with fetch instead of relying on a raw HTML page reload. This pattern is the foundation that every larger form library and framework form-handling feature (React Hook Form, Formik, and similar) is ultimately built on top of.
submitButton.addEventListener("click", function (event) { event.preventDefault(); // ... });
form.addEventListener("submit", function (event) { event.preventDefault(); // ... });
Listening on the button's click misses submissions triggered by pressing Enter in a text field, and still lets a default click-driven submission slip through in some cases. The submit event on the <form> itself is the one reliable hook for every way a form can be submitted.
[Multiple Choice] Where should you attach a "submit" event listener to reliably catch a form being submitted, whether by button click or by pressing Enter? A. On the submit <button> B. On the <form> element C. On the <input> that has focus D. On window
B. On the <form> element — The submit event fires on the <form> element itself regardless of how submission was triggered, making it the one reliable place to intercept it.
[True / False] new FormData(form) requires you to manually list every field name you want to read. A. True B. False
B. False — False — FormData automatically collects every named, submittable control in the form in one step; you only specify a name when reading a particular value back out with data.get("name").
Write a submit handler for a <form id="login-form"> containing #username and #password inputs. Prevent the default submission, and if either field is empty, log "Both fields are required" and stop; otherwise log the two values and reset the form.
const form = document.querySelector("#login-form"); const username = document.querySelector("#username"); const password = document.querySelector("#password"); form.addEventListener("submit", function (event) { event.preventDefault(); if (username.value.trim() === "" || password.value.trim() === "") { console.log("Both fields are required"); return; } console.log("Logging in:", username.value, password.value); form.reset(); });
Trim each value and check for an empty string before deciding whether to log the values or the error.