Forms + JavaScript

Course 2 · Ch 10
Forms + JavaScript
Hooking into the validation built-ins from Chapter 1, and reading submitted data with the FormData API

Chapter 1 of this course covered native browser validation (required, pattern, etc.) — entirely declarative, no JavaScript needed. This chapter covers where JavaScript genuinely adds value on top of that: custom validation messages, validating logic too complex for built-in attributes alone, and reading a form's data without manually grabbing every field one at a time.

The Constraint Validation API — JavaScript Meets Built-In Validation

Every form-related element exposes methods and properties that hook directly into the native validation from Chapter 1, rather than replacing it entirely.

Property/MethodWhat it does
.checkValidity()Returns true/false — does this field currently satisfy its built-in constraints?
.validityA detailed object describing exactly WHY it's invalid (valueMissing, patternMismatch, tooShort, etc.)
.setCustomValidity(msg)Overrides the browser's default error message with your own custom text
.reportValidity()Triggers the native validation UI to display immediately, as if the form had been submitted
// Custom validation message, still using the browser's native validation UI const input = document.querySelector('#username'); input.addEventListener('invalid', () => { if (input.validity.valueMissing) { input.setCustomValidity('Please tell us what to call you!'); } else { input.setCustomValidity(''); // clear it once valid again } });
setCustomValidity must be cleared once the field becomes valid again
A custom message set once stays in effect indefinitely — even after the user fixes the field — unless explicitly cleared with setCustomValidity(''). Forgetting this step is the single most common bug with this API, leaving a field permanently marked invalid even when its actual value is now correct.

Validation Beyond What Built-In Attributes Can Express

Some rules genuinely can't be expressed with pattern or min/max alone — comparing two fields against each other is the classic example.

// Password confirmation — comparing TWO fields, impossible with built-in attributes alone const form = document.querySelector('form'); form.addEventListener('submit', (event) => { const password = document.querySelector('#password'); const confirm = document.querySelector('#confirmPassword'); if (password.value !== confirm.value) { confirm.setCustomValidity('Passwords do not match'); confirm.reportValidity(); event.preventDefault(); // stop the actual submission } });

FormData — Reading Submitted Values Without Grabbing Each Field

Rather than manually querying every input by ID and reading .value one at a time, FormData reads an entire form's submittable values at once, keyed by each field's name attribute (the exact attribute Fundamentals Chapter 7 established as essential for submission).

const form = document.querySelector('form'); form.addEventListener('submit', (event) => { event.preventDefault(); // handle it ourselves instead of a normal page navigation const data = new FormData(form); console.log(data.get('email')); // the value of the field named "email" console.log(data.get('username')); // the value of the field named "username" // Iterate over every field at once: for (const [key, value] of data.entries()) { console.log(key, value); } });

Sending FormData Without a Page Reload

const data = new FormData(form); fetch('/api/submit', { method: 'POST', body: data });

Passing a FormData object directly as a fetch request's body automatically sets the correct content type and encoding, including any file inputs (Fundamentals Chapter 5's type="file") — no manual serialisation needed.

Client-side JavaScript validation is still just a UX layer — Course 1's server-side warning still applies
Everything in this chapter runs entirely in the browser and can be bypassed exactly like the built-in HTML validation from Chapter 1 — disabled JavaScript, a direct API call, or a modified request all skip it completely. Every value still needs genuine server-side validation regardless of how much client-side JavaScript validation exists on top.

Chapter 10 Quick Reference

  • checkValidity()/validity/setCustomValidity()/reportValidity() — hook into native validation rather than replacing it
  • Always clear setCustomValidity('') once a field becomes valid again — the most common bug with this API
  • Use JS validation for logic built-in attributes can't express — comparing two fields, cross-field rules
  • FormData — reads all submittable values at once, keyed by each field's name attribute
  • new FormData(form) passed directly as a fetch body — handles encoding and file uploads automatically
  • Client-side JS validation is still bypassable — server-side validation of every value remains required regardless
  • Next chapter: web components intro — custom elements & shadow DOM basics, the lead-in to Course 3