JavaScript: Preventing Double Form Submission
If you've been in the webmaster world for a while then you'll know that your users operate according to Sod's Law - "If anything can go wrong, it will". This page addresses one of the less common, but still quite annoying problems, double-clickers.
A quick double-click on a button or link can have unintended consequences, such as a form being submitted twice, or a script running in parallel to itself and encountering race conditions. So how can we prevent it?
Disabling Form Buttons
In this example, you can click the first button as many times as you want, while the second button will only respond once.
In practice each click can cause a form to be submitted or some other event triggered every time. For the second button we've added some JavaScript so that it will only accept a single click and ignore all subsequent clicks.
The trick is to use JavaScript to set the disabled property of the button to true.
The disabled property was first introduced by Microsoft but has since been adopted as a standard by the W3C and adopted by all web browsers.
For a simple form, with no associated JavaScripts, you can use:
<form method="POST" action="..." onsubmit="myButton.disabled = true; return true;">
...
<input type="submit" name="myButton" value="Submit">
</form>
This way when the form is submitted - either by clicking on the submit button or pressing Enter in a text input field - the submit button will be disabled to prevent the form being submitted more than once..
With a form handler
If you're already using JavaScript form validation then the command can instead be added to your validation script as follows:
<form method="POST" action="..." onsubmit="return checkForm(this);">
...
<input type="submit" name="myButton" value="Submit">
</form>
<script>
var checkForm = function(form) {
//
// validate form fields
//
form.myButton.disabled = true;
return true;
};
</script>
It's important that the command to disable the button be added at the end of the validation script right before submission. Otherwise, if the validation fails, the user will have no opportunity to re-submit the form.
Using the above code, after the form has been submitted, if the user clicks the browser [Back] button then the submit button remains disabled rendering the form unusable. If you don't want this, read on for some work-arounds.
More user-friendly
Rather than simply disabling the button, we can also change the text so that people don't get confused. This example first disables the button and then changes the label from "Submit" to "Please wait...". The second button restores the initial state:
The code executed when the form is submitted includes the following:
<script>
var checkForm = function(form) { /* Submit button was clicked */
//
// check form input values
//
form.myButton.disabled = true;
form.myButton.value = "Please wait...";
return true;
};
var resetForm = function(form) { /* Reset button was clicked */
form.myButton.disabled = false;
form.myButton.value = "Submit";
};
</script>
While the HTML for the form itself looks something like:
<form method="POST" action="..." onsubmit="return checkForm(this);">
...
<input type="submit" name="myButton" value="Submit">
<input type="button" value="Reset" onclick="resetForm(this.form);">
</form>
A triple-click solution
With this slightly more elegant solution the Submit button cycles through three separate states:
- the Submit button is active and clickable and will submit the form;
- the Submit button is clickable, but will generate an alert message; and
- the Submit button has been disabled.
In the default state, clicking on the Submit button will call our form validation script (see below) to make sure that all the fields have been populated. If that is not the case then there is an alert message and the script halts.
When the form passes validation the button text is changed to "Submitting form..." and a JavaScript variable, form_being_submitted is set to true indicating that the form submission is in progress.
A second click on the Submit button will generate an alert message and disable the button. Subsequent clicks will be ignored. The difference from previous examples is the alert being displayed when the button is first disabled.
Our form validation script is being called from an onsubmit event handler so it's invoked only after all the HTML5 form validation requirements have been met.
Source code:
<form method="POST" action="..." onsubmit="return checkForm(this);">
<p>First Name: <input type="text" required name="firstname"></p>
<p>Last Name: <input type="text" required name="lastname"></p>
<p><input type="submit" name="myButton">
<input type="button" value="Reset" onclick="resetForm(this.form);"></p>
</form>
<script>
var form_being_submitted = false; /* global variable */
var checkForm = function(form) {
if(form_being_submitted) {
alert("The form is being submitted, please wait a moment...");
form.myButton.disabled = true;
return false;
}
if(form.firstname.value == "") {
alert("Please enter your first and last names");
form.firstname.focus();
return false;
}
if(form.lastname.value == "") {
alert("Please enter your first and last names");
form.lastname.focus();
return false;
}
form.myButton.value = 'Submitting form...';
form_being_submitted = true;
return true; /* submit form */
};
var resetForm = function(form) {
form.myButton.disabled = false;
form.myButton.value = "Submit";
form_being_submitted = false;
};
</script>
In the HTML for the form the onsubmit handler first checks to see whether the form is already in the process of submitting (second click). If so it displays an alert and disables the submit button to prevent further attempts to submit the form.
If the form is not already being submitted (first click) the handler runs the usual JavaScript validation and, if successful, allows the form to be submitted.
The Reset button and script are just copied from the previous example, with the one addition being that the form_being_submitted variable is also reset back to false.
For an explanation of the required attribute see our article on HTML5 Form Validation.
Using External JavaScript
We're not changing any of the functionality here, just preparing the JavaScript so it can be moved to an external JavaScript file, and doing away with the necessity for global variables.
First, we strip out any event handlers from the HTML, and assign id values to the elements needing event listeners.
<form id="test_form" method="POST" action="...">
<p>First Name: <input type="text" size="32" required name="firstname"></p>
<p>Last Name: <input type="text" size="32" required name="lastname"></p>
<p><input type="submit" name="submit_button">
<input id="reset_button" type="button" value="Reset"></p>
</form>
There is no actual need to assign an id to each and every form element. Just those with event handlers, and even then we could make do with only the FORM having an id if we wanted.
The next step is to define and assign our event handling functions. The main difference here is that each function receives a variable e representing the event, from which e.target can be used to identify which element was targeted.
And in the form validation script instead of return false; we use e.preventDefault(); which prevents the form from submitting, and then return; to exit the script. There is no need for a return true; at the end.
Finally, we wrap all the code in a DOMContentLoaded function allowing us to create variables without them becoming global. This also delays the script initialisation until the HTML is fully loaded.
<script>
window.addEventListener("DOMContentLoaded", function(e) {
var form_being_submitted = false;
var checkForm = function(e) {
var form = e.target;
if(form_being_submitted) {
alert("The form is being submitted, please wait a moment...");
form.submit_button.disabled = true;
e.preventDefault();
return;
}
if(form.firstname.value == "") {
alert("Please enter your First Name");
form.firstname.focus();
e.preventDefault();
return;
}
if(form.lastname.value == "") {
alert("Please enter your Last Name");
form.lastname.focus();
e.preventDefault();
return;
}
form.submit_button.value = "Submitting form...";
form_being_submitted = true;
};
var resetForm = function(e) {
var form = e.target.form;
form.submit_button.disabled = false;
form.submit_button.value = "Submit";
form_being_submitted = false;
};
document.getElementById("test_form").addEventListener("submit", checkForm, false);
document.getElementById("reset_button").addEventListener("click", resetForm, false);
}, false);
</script>
The JavaScript can now be included either inline on the page or in a separate JavaScript file referenced by the SCRIPT tag. And it will not interfere with any other scripts on the page because the DOMContentLoaded event listener creates a closure and the contained functions are all defined as local variables.
If you plan to include the JavaScript inline then instead of using a DOMContentLoaded event handler you can use an IIFE (Immediately Invokable Function Expression) as illustrated here:
(function() {
var form_being_submitted = false;
const checkForm = (e) => { ... };
const resetForm = (e) => { ... };
document.querySelector("#test_form").addEventListener("submit", checkForm);
document.querySelector("#reset_button").addEventListener("click", resetForm);
})();
In this case the code needs to be place inline anywhere after the FORM - usually at the bottom of the page before the closing BODY tag. It can't be made external or it might execute before the DOM is ready for the event handlers to be assigned.
Preventing double-clicks on links
While the standards suggest that a normal HREF link shouldn't trigger any action other than navigation, it is often the case that links do trigger an action, in which case the same problems can arise from people double-clicking.
A quick fix for this is to add an onclick handler to the links that disables it after the first click preventing subsequent clicks.
Here is a simple example where any links on the page with a class .click-once will be disabled after the first click:
<ul>
<li><a class="click-once" href="#">click me once</a></li>
<li><a class="click-once" href="#">click me once</a></li>
<li><a href="#">click me forever</a></li>
</ul>
<script>
const clickStopper = (e) => { e.preventDefault() };
document.querySelectorAll(".click-once").forEach(current => {
current.addEventListener('click', (e) => { current.addEventListener("click", clickStopper) });
});
</script>
Working Demonstration
In this scenario the first click on the link will function normally taking the user to the target href. At the same time it adds a new event listener to capture and disable future clicks on the same link.
- First click on the link:
- assigns the 'clickStopper' event handler for subsequent clicks
- follows href
- Subsequent clicks:
- calls 'clickStopper' which prevents the href from being followed
Note that we've used ES6 arrow function syntax for our functions here which may not work in older browsers. But the code can always be re-written to vanilla JavaScript if that is a problem.
References
Related Articles - Form Validation
- HTML HTML5 Form Validation Examples
- HTML Validating a checkbox with HTML5
- JavaScript Preventing Double Form Submission
- JavaScript Counting words in a text area
- JavaScript Date and Time
- JavaScript Password Validation using regular expressions and HTML5
- JavaScript Tweaking the HTML5 Color Input
- JavaScript Form Validation
- JavaScript Credit Card numbers
- JavaScript Allowing the user to toggle password INPUT visibility
- JavaScript A simple modal feedback form with no plugins
- PHP Protecting forms using a CAPTCHA
- PHP Basic Form Handling in PHP
- PHP Measuring password strength
- PHP Creating a CAPTCHA with no Cookies
Ben Nadel 26 October, 2022
This is great. I've spent so many years writing code to prevent double-submission inside of AngularJS / Angular apps that I had forgotten how to do it when JavaScript wasn't controlling the whole environment. Thanks!
Jaime Hablutzel 24 November, 2016
What is the HTML (or related) standard behavior expected for the browser if the user makes double click on a submit button? I'm seeing that IE11 process a double click as only one click but Google Chrome interprets double click as two submit operations actually.
A click on the submit button submits the form, but depending on your browser and connection speed the window of opportunity for a second click can vary from milliseconds to seconds. Some modern browsers may also have taken measures to prevent duplicate submission.
The reason to use JavaScript is that it disables the submit button instantaneously. A system of single-use client/server tokens can do this as well if you want to avoid relying on JavaScript.
Nicola 26 June, 2014
Hi, I use Fast Secure Contact Form and I like your second solution. BUT, Where I applied? I'm a really noob. Can you explain where I cut/paste your code? I have some field validation, can I disable submit button after all validations are OK?
BW 12 June, 2014
this doesn't work in Chrome.... fyi.. disabling a submit button stops form submittal
The examples definitely work in Chrome.
Jeremy 24 August, 2013
This is a good solution, HOWEVER, it doesn't really stand well in 2013. It isn't really compatible with HTML5's input "required" attribute. If an input has the required tag, and you press the submit buton, and the field is empty the browser will fire the "Please fill out this field" message, BUT, you also just disabled that submit button. So in effect, the form can no longer be submitted.
This below solution works,
For instance, here is the text input:
<input type="text" id="first_name" required />
Using the 3. A triple-click solution solution above, the submit buton needs an id assigned, in addition to the name:
<input type="submit" name="myButton" id="myButton" onclick="
And this jQuery script is need for EVERY input with the required tag:
document.getElementById('first_name').addEventListener('invalid', function() {
submitting = false;
document.getElementById('myButton').value='Submit';
}, false);
You're right. The solution is to remove the onclick event handler from the submit button and replace it with an onsubmit handler on the form, as we've done now.
Erik 20 April, 2013
The disabled state hangs in FF if you use the backbutton. Try clicking button 2 in the first example, visit another page and navigate back via the backbutton. Button 2 is still disabled.
izwan 14 June, 2012
thanks dude, the third one sure saved me
Dave 23 February, 2008
I am trying to make this work with a page that has JS Code for validation. I placed this at the end of the script. It disabled the button, but the other validation does not work. I know I have missed a step.
I think you need to first read the JavaScript Form Validation article. You seem to have pasted the 'double submit' code outside of any function, so it's being called when the page loads instead of only during form validation.