knowledge base/practices

Controls rebuilt when an async reply arrives lose the click that lands in between

Fact

A view that draws its own buttons (tabs, filters) in the same render() it calls when fetched data arrives replaces those buttons under the user. A press that starts on the old node and ends after the swap, or a click aimed at a node that was just removed, reaches nothing: no handler runs and no error is thrown.

Why it matters

The window is the network round trip after opening the view, which is exactly when people tap a tab. By hand it looks like a tap that "didn't take"; in browser automation it fails intermittently as Node is detached from document (Puppeteer) or a stale element reference (Selenium), which reads as a flaky test rather than a bug in the view.

How to apply

  • Build the controls once, when the view opens, and keep references to them. A render only updates their state (classList.toggle('on', ...), disabled, aria-pressed) and replaces the data region beside them.
  • Re-rendering a control is fine when the user's own action caused it; what breaks is a redraw triggered by something the user did not do, such as a reply, a timer or a push.
  • A test that clicks a control right after opening a view that fetches is the cheapest check: it fails on the detached node whenever the reply wins.