There always will be time between the first click and the time the button gets disabled and even more time before the visual representation of the button gets updated to reflect that. Keeping that time so short that it is impossible for a human to click the button again can be very hard.
It would help if GUI elements had a property “automatically disable on click”, removing the need for the “on click handler” to disable the button (in exchange for adding the need to explicitly re-enable it).
I don’t remember seeing GUI libraries that do that, though.
That probably is because it would confuse users if buttons visually get disabled when they click them.
So, the best answer is to visually keep the button enabled, but ignore rapid further clicks. That’s debouncing.
[dead]
This is a fundamental misunderstanding of how GUIs must necessarily work. There should be no possibility of race conditions if you understand the threading model.
The visual representation updating (greying out button) is a result of disabling the button, not the same thing. In virtually every GUI toolkit I've ever used there is the concept of the main UI thread, and everything that happens (input and display updates) necessarily has to go through that single thread in order to ensure correctness. (This applies to browsers, too.) That's why input goes into a queue, so you can easily do things like:
(All on the main UI thread):