What a screenshot cannot show
A search page can look finished while an old response replaces newer results, Back loses the query, or a failed save appears successful. Button colors and alignment matter, but they do not tell us whether those interactions work.
The browser holds state, communicates with services over an unreliable connection and accepts new input while earlier requests are still running. Frontend estimates and code reviews need to account for that behavior.
Take an ordinary issue list
Imagine an issue tracker. Someone selects Open, searches for ‘invoice’, opens an issue and presses Back. They expect the same query, filter and position in the list. They might also switch projects or lose their connection during that sequence.
The URL can describe the project, query, filter and page. Unsubmitted input needs its own state. Results belong to a request; a cached response may be usable but old. A pending status change belongs to one issue. Decide which part of the application owns each value.
For this example, I would make the submitted search addressable in the URL and keep the unfinished input separate. That gives refresh and copied links a defined meaning. It also avoids treating every keystroke as a separate destination in browser history.
A successful response can still be wrong
Suppose the request for 'invoice' starts, then the person changes the query to 'invoice overdue'. The second request finishes first. If both responses are allowed to replace the visible list, the older response can overwrite the newer one. Both HTTP requests succeeded. The application still produced an incorrect result.
A response should update the visible results only if it belongs to the active query. AbortController can cancel a superseded fetch. A request identifier or stale-result guard can prevent an obsolete response from being applied. React’s effect documentation shows cleanup that ignores a response after its request is no longer relevant.
Debouncing reduces the number of searches but does not determine which result is current. Cancellation does not undo a server operation that has already happened.
A cache and a Back button need rules
A cache entry called 'issues' is too vague if results depend on the project, status, query, and page. The cache key needs those distinctions, plus the relevant account scope. Otherwise a fast cache hit can show the wrong list. Switching accounts also needs an explicit policy for clearing or separating cached data.
Returning from an issue detail page raises another choice: show the cached list immediately, wait for fresh data, or show the cached list while refreshing it. Each can be reasonable. If background refresh fails, the interface should distinguish previously loaded results from a search that returned no results.
Browser history is part of this design. The History API offers pushState to add an entry and replaceState to update one; popstate lets an application respond to traversal. A router may manage the mechanics, but the product still needs to decide which changes deserve an entry and what state to restore. Pressing Back after reading an issue should not force someone to reconstruct their search.
Recovering from a failed update
Now let the person close an issue from the Open list. Removing the row immediately can make the action feel responsive. Until the server confirms it, though, that disappearance represents a pending operation. The client needs to remember enough to reconcile success or recover from failure.
Restoring a snapshot of the entire list on failure is dangerous if another issue changed in the meantime. It can erase newer successful work. Track the pending mutation by issue and operation, then reconcile it against the current state. If the person closes and reopens the same issue quickly, decide whether to serialize those operations or use a version-aware conflict policy. Arrival order alone is a poor substitute.
A timeout is ambiguous: the server may have accepted the write even though its response never reached the browser. Retrying can repeat a notification or create a duplicate record. Agree how the API handles retries, whether it needs an idempotency key and how the client can retrieve an accepted result.
Review the behavior, including failures
These concerns overlap with backend engineering: concurrent operations, cache invalidation, retries, and consistency. The responsibilities differ. The server must enforce authorization on every request and protect the stored data. Hiding a Close button in the browser cannot enforce permission. The client must then explain rejected or conflicting operations without pretending they succeeded.
For this issue list, I would make the first search finish last, fail a write after another succeeds and navigate Back during a pending request. I would also check that keyboard focus survives a disappearing row and that an error is announced to assistive technology.
Include these cases in the estimate and the acceptance criteria. The screen, requests and stored state all need to be checked before the feature is finished.
Sources & further reading
Sources checked on 17 September 2026.