4 ms·
Basically the whole "disable submit button after submit" thing is a lazy hack. Double submit should be prevented by other means and "disabled" is semantically
by moritzwarhier 3y ago
Basically the whole "disable submit button after submit" thing is a lazy hack.
Double submit should be prevented by other means and "disabled" is semantically different from "this action is in progress".
This problem can basically only occur for async form submits, so one can easily just alter the button and replace it with a dummy like ("submitting") in any way it seems fit, using JS.
One can also set a "submitting" CSS class for the UI state and change text accordingly (or aria-label).
For old-fashioned plain HTML form submits, the browser handles the client side of the double-submit problem already, no?
- Spivak 3y ago> Basically the whole "disable submit button after submit" thing is a lazy hack. It is and there is a ready-made solution to handle it https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ET... Your HTTP library likely already has support for it.
- hombre_fatal 3y agoHow would etags help you avoid multiple inflight requests from users double submitting a form?
- mgkimsal 3y agoPotentially snarky answer: they don't/can't. A double click is something people do within a split second. Within a few ms, your application is now trying let's say, to store a comment and send a notification. Even with some sort of one-time token, process A will check the token, mark it 'used', but process B may be doing the same thing, and when process B started, the token was valid then too. If stored in SQL, you can try to do something with locking the table or row, and a second process might not get to update (or you can look in to optimistic locking/versioning/etc) but it's a thorny problem to deal with when people 'double click' and two processes start within a few ms of each other. tldr: disabling a submit button to prevent a double click scenario is by far the easiest and most pragmatic approach that stops the problem from entering "much more complex/difficult" territory. Ideally, the 'disabled' state might auto-undisable after a couple seconds, but the original request handler should be able to reset the state after processing the response. Would be so much nicer if this was a standard attribute to add in to forms to prevent this (2 single clicks registered back to back within X ms... ignore second). edit: finished the article, and the 'data-submitting' approach is something I also have implemented some times, and works decently well as an alternative. You're still having to prevent double submission. etags won't really help there.
- Nextgrid 3y agoRedis should be able to handle tens of thousands of reads/writes without much issue on contemporary hardware. Every request that comes in contains a random "idempotency token" (can be in a hidden form field) and upon reception the server checks the previous value & sets the key if it's not there (GETSET command on Redis should do it?) The presence of the previous value indicates whether this token was handled already (if so it means duplicate request and return an error/etc), and setting the value will ensure any subsequent duplicate request will get rejected.
- SebRollen 3y agoEDIT: My reply below is wrong - you _can_ deal with in-flight requests with E-Tags as well using the if-match header. I had only seen them used with if-none-match. TIL. E-Tags would probably not help here, because the client won't have a tag to send along with its request until after the original response returns. But the thing we're trying to prevent in the first place is multiple concurrent requests being processed. The Idempotency-Key header[1] is probably a better fit, but that relies on the server implementing the spec, including properly dealing with multiple in-flight requests with the same header. I found this blog post[2] pretty good for exploring some of the implementation challenges. [1]: https://www.ietf.org/archive/id/draft-ietf-httpapi-idempotency-key-header-03.html https://www.ietf.org/archive/id/draft-ietf-httpapi-idempoten.... [2]: http://live.julik.nl/2021/12/actually-creating-a-gem-for-idempotency-keys http://live.julik.nl/2021/12/actually-creating-a-gem-for-ide...
- hombre_fatal 3y ago> For old-fashioned plain HTML form submits, the browser handles the client side of the double-submit problem already, no? No, users can still double-click the submit button to create multiple requests. I don't see how you can solve it without JS.
- 542458 3y agoI believe it is solvable without JS - you can’t prevent the request from being created, but you can prevent the second submission for having any effect. You could add a hidden UUID to the form server side and only process any submission with a given UUID once. I believe something similar is commonly done for anti-CSRF purposes. Alternatively, just rate limit users. A user making multiple comments within a few seconds of each other is probably either accidentally double-submitting or spamming. I think this is what HN does.
- feoren 3y ago> You could add a hidden UUID to the form server side and only process any submission with a given UUID once. Not if you have a load balancer that might send the two requests to different servers. If all of your requests are idempotent (difficult to pull off, but often worth it), you might be able to just let both servers handle it. But you still have your users inadvertently hogging resources they don't need. And if your request is taking a long time, a pissed-off user could be spamming the submit button in frustration (I'm sure I've done this before). You should absolutely prevent the 2nd submit in JavaScript, but I'll accept that disabling the button is not the best approach for that. > I believe something similar is commonly done for anti-CSRF purposes. That's a crypto-signed token you can use to verify you've pre-authorized the submission, not just some UUID. (You could do it with UUIDs if you keep a list, but ... don't)
- 542458 3y ago> That's a crypto-signed token you can use to verify you've pre-authorized the submission, not just some UUID. (You could do it with UUIDs if you keep a list, but ... don't) I'm struggling to remember the exact details here, but I believe Drupal 7 does what I described and just keeps a big list (expiry on entries set to 6 hours) in their database. I remember having issues with it getting very large when we had issues with bad scrapers hammering our site. Part of the issue though is that Drupal tracks the entire form (in order to prevent form tampering issues) and not just the UUID, which makes the table much larger than necessary for the use case of preventing CSRF and double-submits. https://drupal.stackexchange.com/questions/69803/cache-form-table-size-is-enormous/69842#69842 https://drupal.stackexchange.com/questions/69803/cache-form-...
- eviks 3y agoit's not a lazy hack, but a useful indication to the user that his action has an effect. Besides double submit there is also an issue of a single sumbit - have I actually clicked on the button or somewhere nearby or never properly clicked or has something else, pardon the pun, not clicked?
- withinboredom 3y agoWorse is when the request fails but the JS written doesn't know how to handle that and now I can't try again.
- moritzwarhier 3y agoYou imply that this is the only possibility to provide user indication for a form submit - why? What about some stupid text below the button: "Your data is being submitted..."? Seems the meat of the argument is the disabling behavior, which is really trivial to implement in JS. When I want to submit a form and the button changes to a regular grayed-out disabled button on click, on top of that without a page reload, I'd assume something went wrong. You need to distinguish "disabled" from "action in progress" any way, by using a bit of special casing and CSS polish for the "submitting disabled" button... in which case, why use the disabled attribute at all, instead of preventing submit in JS? It's not harder compared to toggling attribute in JS.
- eviks 3y agoI don't imply that, you can add some text, it would still not make the original fundamental UI feedback benefit disappear and make it a lazy hack. The text is ok as an addition, but worse as a substitute - the feedback should be a property of the UI element you interact with. > , by using a bit of special casing and CSS polish for the "submitting disabled" button.. or by simply adding a different label to the now disabled button? > why use the disabled attribute at all to get the same outcome in a simpler manner? But if you recreate all the pieces with in an alternative way, that's fine, don't toggle the attribute
- moritzwarhier 3y ago
- iteratethis 3y agoThe problem occurs for any type of form submit, not just async. Replacing a submit button with a dummy is wrong, it does not solve the accessibility issue where focus is lost or moves.
- moritzwarhier 3y agoRe replacing the element and losing focus: Yeah that's true, I was writing this from my lunch break in a hurry and this was sloppy/wrong. What I have actually opted for so far in SPAs and other JS form submits is to show a loading indicator inside the button tag, and ignoring the function call as long as a request is pending. Sibling comments and the article suggest equivalent solutions. Re sync form submit: OK, it is still possible to trigger double submits, youre right. I'd argue that 99% of the users notice their browser's native loading indicator though. Also I have personally experienced the "do you want to submit this POST request again?" popup for spamming form submit buttons without manually reloading the page, but I'm not sure how reliable that is or what the preconditions are. This being possible is directly caused by browser trying to appear faster by not replacing the current page until they can start rendering the next one. Like with bfcache, vendors do their best to make full page reloads feel like an SPA if the site performance allows. Which brings us to the only sane answer: if you need to reliably prevent double submits against malicious or clueless users, you need to act on the server. You can't de-duplicate HTTP requests with client code.
- andrewflnr 3y ago> "disabled" is semantically different from "this action is in progress" Different, but not contradictory. Disabling just means "user can't do that", which is semantically reasonable when you don't want the user to do that. That stays true when the ultimate cause is that they already did that.