How to prevent a button from submitting a form?
A button inside a form submits it by default. Add type=button, or handle the submit event with preventDefault, in plain JavaScript and in React.
Add type="button" to the button. A <button> inside a <form> without a type is a submit button, so every button that does something else, like showing a password or adding a row, needs the type written out.
<form class="signup-form" action="/signup" method="post">
<label for="password">Password</label>
<input type="password" id="password" name="password">
<button type="button" class="toggle-password">Show</button>
<button type="submit">Sign up</button>
</form>Now clicking "Show" runs only your click handler, and "Sign up" still submits. Writing type on every button, even the submit one, makes the intent obvious to the next person who reads the code.
Handle the submit yourself
If you want the form to submit, but with JavaScript instead of a page reload, listen for submit on the form and call event.preventDefault():
const form = document.querySelector('.signup-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const response = await fetch(form.action, {
method: 'POST',
body: new FormData(form),
});
if (response.ok) {
form.reset();
}
});Listen on the form, not for click on the submit button. A form can be submitted without clicking that button, most often by pressing Enter in a text field, and the submit event catches every way. It also fires only after the browser's built-in validation has passed, so required and type="email" keep working.
For the same reason, call form.requestSubmit() rather than form.submit() when you trigger a submit from code. submit() skips validation and doesn't fire the submit event, so your handler never runs.
In React
The same idea with onSubmit:
function SignupForm() {
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
await fetch('/api/signup', { method: 'POST', body: formData });
}
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" required />
<button type="submit">Sign up</button>
</form>
);
}Read event.currentTarget before the first await. After it, currentTarget is null.
In React 19 you can also pass a function to the form's action prop. React calls it with the FormData and doesn't reload the page, so there's no preventDefault() to write.
Enter submits the form too
Pressing Enter in a text input submits its form. If the form has a submit button, the browser does it by clicking the first submit button in the form. That leads to a sneaky bug: when a "Show password" button without a type comes before the real submit button, pressing Enter in any text field of that form runs the toggle's click handler as well.
If the form has no submit button, Enter only submits when there's a single text field.
Keep Enter-to-submit for normal forms, because people expect it. If one field really shouldn't submit, for example a search box with suggestions where Enter picks one, stop it on that input:
input.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.isComposing) {
event.preventDefault();
}
});The isComposing check leaves Enter alone while someone is typing with an input method editor, for example in Japanese or Chinese, where Enter confirms the characters.