If you’ve clicked “submit” on any HTML form, you’ve probably encountered a moment of uncertainty that comes before you're redirected to the “thank you” page. Even though it lasts only for a few moments, it can bring up a lot of questions such as "is the form not working?", "was my click registered?" or even "is the internet down?". Sometimes users, out of panic, end up clicking the submit button again, leading to duplicate submissions.
The delay creates an off-putting user experience. On top of that, a second click results in a duplicate request to your Formspree form (which gets counted against your usage quota) and a duplicate record in your submissions list (that you’ll need to clean up later). This is why we’ve put together this short guide to help you understand why this happens and how you can fix it with a short JS snippet.
Why Does This Happen?
The delay your users experience after clicking the “submit” button on a Formspree form is not caused by a problem with Formspree itself; it is a natural result of how web forms work. Here’s a quick rundown of the process: When a user clicks the “submit” button, the browser initiates an HTTP request to the backend with the form data. This request travels through the internet to reach the backend during which it can run into latency due to slow network connections.
Once the request reaches the servers, it needs to be processed. This can involve validating the information, storing it in the database, and potentially sending confirmation emails. This processing also takes time, although typically less than the network transfer. Once done, the backend responds with a confirmation and redirects the user to the “thank you” page.
The time taken by the request to travel to and from the server causes a delay on the user’s end after clicking the “submit” button. Moreover, slower connections like mobile data networks can add to the latency of the request and enhance the problem.
Here’s what the delay looks like:
How Can You Fix It?
While there’s nothing you can do about the time spent during the HTTP request, you can, however, implement a better waiting experience for your users. If you are developing a React-based (or Next/Gatsby-based) project, you can make use of the state
object returned by the useForm
hook from the Formspree React library to know whether the form is currently being submitted and disable the submit button. However, in vanilla JS, you need to implement this check by yourself.
To do that, you need to listen for the form submit event which is fired whenever a form is submitted in a webpage. Once you receive this event, you need to disable the submit button and/or display a UI element (such as a spinner) informing the user that the form data is being processed in the background.
NOTE: While you could implement the same logic using a click event on the “submit” button as well, the “submit” event on the form is a more reliable way of capturing all possible ways of submitting the form, which include clicking the “submit” button, pressing
Enter
when editing a field, and programmatically submitting the form by calling theform.requestSubmit()
method.
Disable the Button Upon Submission
The simplest way to inform the user about background processing is to disable the submit button and optionally show a waiting sign on the cursor when hovering over the button. This is how it would look like:
To implement this, you will need to set a greyed-out background and the wait
cursor for the disabled state of the button through CSS. Next, you need to listen for the submit
event on the HTML form and set the disabled
attribute for the submit button as true
when the submit
event is observed.
Here’s what the HTML file would look like:
<html>
<head>
<style>
/* Other styles omitted... */
/* 1. Set the background and cursor for the disabled state of the button */
#submitButton:disabled {
cursor: wait;
background: #dfdfdf;
}
</style>
</head>
<body>
<form id="contact-form" name="simple-contact-form" accept-charset="utf-8" action="https://formspree.io/f/{form_id}"
method="post">
<label for="full-name">Full Name</label>
<input type="text" name="name" id="full-name" placeholder="First and Last" required="">
<label for="email-address">Email Address</label>
<input type="email" name="_replyto" id="email-address" placeholder="email@domain.tld" required="">
<label for="message">Message</label>
<textarea rows="5" name="message" id="message"
placeholder="Your message here"
required=""></textarea>
<input id="submitButton" type="submit" value="Submit">
</form>
<!-- 2. Add the following script to listen to the submit event -->
<script>
const form = document.getElementById('contact-form');
const submitButton = document.getElementById('submitButton');
form.addEventListener('submit', function (event) {
// Sets the submit button's disabled attribute as true
submitButton.disabled = true;
});
</script>
</body>
</html>
This method is straightforward and picks up native cursor loaders across platforms. For example, here’s what the loading cursor would look like to a Windows user:
The cursor isn’t visible on mobile devices. However, the submit button is still greyed out so the user would know that their tap was registered and they are not able to tap for a second time:
Replace the Button With Another UI Element Upon Submission
Alternatively, you could hide the submit button entirely and show a UI element such as a spinner in its place when a form is submitted:
The code for this form is pretty straightforward as well:
<html>
<head>
<style>
/* 1. Add the following styles */
#loader-container {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#loader {
border: 4px solid #e5122e;
border-bottom: 4px solid #fff;
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<form id="contact-form" name="simple-contact-form" accept-charset="utf-8" action="<https://formspree.io/f/{form_id}>"
method="post">
<fieldset id="fs-frm-inputs">
<div id="inputs">
<label for="full-name">Full Name</label>
<input type="text" name="name" id="full-name" placeholder="First and Last" required="">
<label for="email-address">Email Address</label>
<input type="email" name="_replyto" id="email-address" placeholder="email@domain.tld" required="">
<label for="message">Message</label>
<textarea rows="5" name="message" id="message"
placeholder="Your message here"
required=""></textarea>
<input type="hidden" name="_subject" id="email-subject" value="Contact Form Submission">
</div>
</fieldset>
<input id="submitButton" type="submit" value="Submit">
<!-- 2. Add the following div tags -->
<div id="loader-container" >
<div id="loader" hidden="hidden"></div>
</div>
</form>
<!-- 3. Add the following script to listen to the submit event -->
<script>
const form = document.getElementById('contact-form');
const submitButton = document.getElementById('submitButton');
const loader = document.getElementById('loader')
form.addEventListener('submit', function (event) {
// Removes the hidden attribute from the loader element
loader.hidden = ""
// Sets the submit button to be hidden
submitButton.hidden = "hidden";
});
</script>
</body>
</html>
You add two <div>
s, one nested inside another, right below the submit button, add the CSS to center the loader inside the container, and set up keyframes for the animation of the loader. Finally, you add the JS script that listens for the form submit event and hides the submit button while unhiding the loader.
This approach gives you more control over the UI design of the loader element. Also, you can keep a consistent design for your loader across platforms.
The script for adding the form submit event listener needs to access the HTML DOM to set up the event listener. So, you need to ensure that the code for adding the form submit event listener
(form.addEventListener('submit', function (event) {})
is run only after the DOM has finished loading. This means that if you are using an inline script, make sure to add it right before the closing</body>
tag, just as in the examples shown above. Alternatively, if you are using an external script, make sure you eitherdefer
its execution until after the HTML DOM is parsed using the defer attribute in the<script>
tag or you listen for theDOMContentLoaded
event in the script before adding the form submit event listener to the document.
Offering a Smooth UX With Your Forms
As you’ve seen, the issue of delay in feedback after clicking the submit button and incidents of duplicate submissions can be easily resolved by implementing UI elements that inform the user about the background processing being done to handle their form submission. Hopefully, this guide helped you understand how it works behind the scenes and provided you with some useful code snippets to get started right away.