
You finish the form and click Submit. The button starts spinning. A few seconds later, it is still spinning.
Should you click again? Will refreshing lose your work? Has the request already gone through?
People should not have to work this out from a loading animation. That small moment on a screen leads to decisions about storage, duplicate requests, and the code someone will maintain later.
Take a meeting-room booking as a hypothetical example. Someone wants one room for one time slot. They need to know what happened to that booking.
The booking may exist before the screen knows
Suppose the server saves the booking, but the connection drops before its response reaches the browser. The screen sees a timeout.
If it immediately says “Booking failed. Please submit again,” it may encourage a second request for work that already happened.
No response is not proof that no change occurred. The screen should report what it can establish: “We cannot check your booking yet,” with a way to look up the existing request. Amazon Builders’ Library explains how retrying can create unwanted extra effects.
Agree on this behavior before writing the button text. A connection problem should not leave the user responsible for guessing the state of the system.
Give each state a useful meaning
A form may need more than Success and Error:
- Sending the request: preserve the entered details and prevent rapid repeated clicks.
- Booking confirmed: show the verified room, time, and reference.
- Booking rejected: explain a confirmed reason, such as an unavailable time slot, and what can change.
- Result unknown: offer a way to check the existing request before starting another.
Even a successful submission might only mean “Request received.” If someone still needs to approve it, show Pending approval rather than Booking confirmed.
Use the same meanings in the form, booking history, and notifications. People should be able to follow one request without translating its status at every step.
A retry needs to remain the same request
Disabling a button reduces rapid clicks. It does not cover refreshes, multiple tabs, or requests arriving together. The server needs to handle duplicates too.
One approach assigns an identifier to each operation. Retrying unchanged input uses that same identifier, so the server can distinguish a retry from a new booking. This is idempotency: repeating an operation should not add another effect each time.
Scope the identifier to the caller and operation, retain normal permission checks, and handle concurrent requests correctly. Checking for an existing record and saving later as separate steps can still allow duplicates.
External services have their own contracts. For example, Stripe documents its idempotency-key behavior, including parameter checks on repeated requests. A key does not replace authorization, and changed input must not silently reuse the previous operation.
Define how long identifiers remain valid, too. Once that window ends, do not assume the system still remembers a request. If its result remains unclear, check the existing operation before deciding to resubmit.
The room-booking system also needs a rule for two different requests competing for one slot. Recognizing a retry and preventing a booking conflict are separate responsibilities.
Clean Code starts with the meaning of the work
If one variable named success means Request received, Record saved, and Booking approved, the next maintainer must read every branch to discover which kind of success it represents.
Names such as requestAccepted, bookingConfirmed, and resultUnknown make the conversation clearer. They still need distinct behavior behind them. Renaming a variable cannot fix an unclear workflow.
The structure should make that workflow easy to follow. The form collects input and presents a result. The booking operation checks permissions and availability, then coordinates persistence. Database and external-service details have clear connection points. This follows the Clean Architecture principle of keeping business rules independent of tool details.
A small system can keep these responsibilities within one application. Extra services are not a requirement for an enterprise project. Ask whether the team knows where a booking-rule change belongs and how to check it.
Test a bad connection, too
Before launch, walk through these cases:
- Submit twice quickly: the same operation must not create two bookings.
- Save the booking, then lose the response: the user can find the original result; the screen does not declare failure without evidence.
- Two people request the same slot together: the outcome follows the agreed rule rather than whichever screen updates first.
- Change the room or time and submit: distinguish new intent from a retry of the earlier request.
- Reject an unauthorized request or encounter an unavailable service: preserve appropriate input and explain the next step without exposing internal data.
Check the rules, real persistence, and the interface. Function tests alone do not cover concurrent storage behavior. Clicking through a screen alone may miss duplicate records underneath it.
Write acceptance criteria as observable results: “Submitting the same request concurrently twice creates one booking.” That gives the team something more useful to verify than “The system must never break.”
Start with one form
Pick a form your team uses often. Ask what it says after submission, where a missing result can be checked, and what a retry does.
If an answer is unclear, improve that behavior before adding another visual effect. Give people a readable screen and a clear way to find out where their work stands.
Continue with software someone else can maintain, or use the project checklist to describe a workflow before talking with INTECH.
