Repository navigation
Conversation
React writes a controlled control's value and checked state back after its change handlers run. Redact only synced them on re-render, so a handler that rejected an edit by not calling setState left the typed text, checkbox toggle, radio selection or select choice in the DOM. The restore runs after the element's last Redact listener for the native input or change event, so onChange, onInput and their capture forms all read the edit, also with real user input, which runs microtasks between listeners. It is queued behind the handler's own updates (microtask, or act's queue), so it reads the props they render and keeps the caret of an accepted edit. Hydration records the props too, and a radio restores the rest of its group. Fixes TanStack#115. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
These controls fire input before change. With onInput and onChange both set, the restore ran after the input listener, and since real clicks run microtasks between events, onChange read the restored state instead of the click. A change listener now owns the restore for them; an onInput alone still restores after input. Refs TanStack#115. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The restore ran in a microtask after the control's last Redact listener, and real input runs microtasks between listeners. So an ancestor's onInput or a later native listener read the restored value, and an ancestor that accepted the edit lost it. A capture listener that stopped propagation also skipped the restore: the target's bubble listener, which owned it, never ran. The control's listeners now mark the native event, and listeners on the root and portal containers restore once it reaches them, as React's root listener does. A stopped or non-bubbling event restores at once, since it never reaches a container. Listening on the container rather than the document keeps the restore when a native listener on the container stops propagation, and covers shadow roots. A checkbox, radio or select with a change listener still restores after change; with only onInput it restores after input. Refs TanStack#115. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Browsers fire no `change` event when a checkbox or radio that is not connected to a document is clicked, for example `input.click()` on an element in a detached container. Redact maps a checkbox's or radio's onChange to the native `change` event, so neither the handler nor the controlled restore from TanStack#116 ran there. React reads checkbox and radio changes from `click`, which also reaches detached elements. The onChange listener now also listens for `click`, but acts on it only while the element is detached, and for a radio only when it was not checked before: its controlled `checked` prop, the checked state the last restore saw, or its default. Attached controls keep their `change` event, so their timing, the effect of preventDefault on the click and native `change` dispatches are unchanged. A full switch to `click` would change all of these for every app to cover a case that only arises outside the document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: c425e2a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Refs #115. Stacked on #116; this covers its last "Not covered" item. Until #116 merges, this diff includes its commits; review only the last one (
c425e2a).Browsers fire no
changewhen a checkbox or radio outside the document is clicked, e.g.input.click()in a container never appended. Redact maps theironChangetochange, so neither the handler nor #116's restore ran. React reads them fromclick(shouldUseClickEvent), which reaches detached elements, and firesonChangewhen its value tracker seescheckedchange.What changes
onChangealso listens forclick, but only acts while the element is detached. Attached controls keepchange, so nothing changes for them.checkedprop, else the state the last restore saw, else its default. The restore then undoes a rejected click.onClickprevents still callsonChange.Switching attached controls to
clicktoo would match React there as well (onChangedespite a prevented click, none for a dispatchedchange), but it would change event order and restore timing for every app to fix a detached-only case.Size
+65 to +97 B gzip against #116; three entries go over budget.
DETACHED_CHANGE_SIZE_ALLOWANCEholds the measured increments and needs your approval. If that's too much for a detached-only gap, I'm happy to close this.Not covered
All detached only:
onChangewritten beforeonClickruns first; React always runsonClickfirst.onChangeafter a handler-less sibling was checked, and repeats it for two clicks with no await between.Testing
Removing the fix fails 10 of the 17 new jsdom tests; the other 7 check cases where nothing may fire or change. The browser test confirms browsers fire no
changefor detached controls, and its detached cases fail without the fix in Chromium, Firefox and WebKit.pnpm test:prpasses on Node 24.22.0, and the actions browser suite passes in all three browsers.I used Claude Code to write the fix and tests, and had it run the suites and a separate review pass.