fix: attach form submit listener before paint to prevent reload flake - #61
Merged
Merged
Conversation
test_form_no_page_reload could fail with render_count == 2 in CI. The Form component attached its submit listener in React.useEffect, which runs *after* the browser paints. That leaves a window after the form is rendered but before the listener is wired, during which a submit falls through to the browser's native GET submission - a full page reload that remounts the app and re-runs the route component, bumping the render counter. Attach the listener in useLayoutEffect instead, which runs synchronously before paint, so the preventDefault handler is always in place by the time the form is visible and interactive. This mirrors the Link preventDefault race addressed for scroll restoration.
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 free
to 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.
Description
test_form_no_page_reloadwas flaky in CI (e.g. run35571256796, python-source 3.13), failing with:Root cause
The
Formcomponent (from #58) wires itssubmitevent listener insideReact.useEffect, which runs after the browser paints. That leaves a window — widest on a slow/loaded CI machine — after the form is rendered but before thesubmitlistener is attached.When a submit fires during that window, the listener's
preventDefault()never runs, so the browser performs its native GET form submission → a full page reload. ReactPy remounts the app and re-runs the route component, so the server-sideRefrender counter reaches 2 (and the client-side router/scroll store is wiped). This is the same class of defect as theLinkpreventDefaultrace that was fixed for scroll restoration in #59.Fix
Attach the
submitlistener inReact.useLayoutEffectinstead ofReact.useEffect.useLayoutEffectruns synchronously before the browser paints, so thepreventDefaulthandler is always in place by the time the form is visible and interactive — eliminating the fall-through-to-native-submit window entirely.Reactispreact/compatfrom@reactpy/client, which providesuseLayoutEffect.Verification
Before the fix, the form-no-reload test reproduced
render_count == 2under load. After the fix:15/15CI-stylehatch test --headlessruns27/27genuine runs undertaskset -c 0,1 -n 6oversubscription (the only 3 "failures" were unrelatedbun installEEXIST harness races from concurrent xdist workers)30/30passed across 3 runsChecklist
Please update this checklist as you complete each item:
By submitting this pull request I agree that all contributions comply with this project's open source license(s).