Uh oh!
There was an error while loading. Please reload this page.
fix(ui): stop Button throwing away a click when something re-renders - #52
Merged
Conversation
A click is only dispatched when mousedown and mouseup land on the same element. Button's inner surface was a component declared inside Button, which makes it a new component type on every render — so React unmounted the content subtree and mounted a fresh one, and any render occurring between the two halves of a press replaced the element the pointer was over. The click never happened. A hover handler on an ancestor is enough to cause that render, which is why it presented as intermittent. Nothing said anything was wrong: the button stayed in the tree, matched by role and name, and reported enabled throughout. It cost two long debugging sessions in the part viewer before the cause was found, and it is currently eating the Delete button in the DFM app. The surface is now an element built by a plain function rather than a component declared during render, so its identity survives. Asserted as node identity rather than by firing a click. fireEvent.click dispatches straight at the element and jsdom does not build a click out of mousedown and mouseup, so a click-based test passes whether the surface was replaced or not — the identity is what the browser actually cares about.
BradEstey
approved these changes
Aug 26, 2026
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Buttonthrows away a click when anything re-renders mid-press. This fixes it.A
clickis only dispatched whenmousedownandmouseupland on the sameelement. The button's inner surface was a component declared inside
Button:A component declared during render is a new component type on every render, so React
unmounts the content subtree and mounts a fresh one each time. Any render occurring
between the two halves of a press replaces the element the pointer is over, and the
click never happens. A hover handler on an ancestor is enough to cause that render,
which is why it presented as intermittent.
Nothing said anything was wrong. The button stayed in the tree, matched by role and
name, and reported enabled throughout. It cost two long debugging sessions in the part
viewer before the cause was found, and it is currently eating the Delete button in
the DFM app — one e2e failure there traces to exactly this.
The surface is now an element built by a plain function rather than a component
declared during render, so its identity survives.
On the test
Asserted as node identity across a re-render, not by firing a click. That is
deliberate:
fireEvent.clickdispatches the event straight at the element, and jsdomdoes not build a click out of
mousedownandmouseup— so a click-based test passeswhether or not the surface was replaced. I wrote that version first and confirmed it
passes against the broken component, which is worth knowing if anyone reaches for it
again.
Verified the test fails against the current
maincomponent and passes against thefix:
Public package release
pnpm changesetand committed the generated Changeset.no-release-neededlabel and explained why below.patch— a bug fix, no API change.Validation
Independent of #51 — different package, no overlap. Either can land first.