Conversation
In StrictMode, React runs effect cleanups and then re-runs effects right after mount. The unmount cleanup in useSelectableCollection cancelled the pending requestAnimationFrame that scrolls the focused item into view, and the re-run scroll effect did not schedule it again, so a ComboBox opened with the keyboard focused the first enabled option without scrolling to it. Only treat the frame as pending until it runs, and if the cleanup cancels a pending scroll, let the scroll effect retry it. Closes adobe#10690
Author
|
The |
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.
Closes #10690
Summary
Intent: when a ComboBox opens with the keyboard and focus moves to the first enabled option, that option should be scrolled into view, even when the disabled options above it fill the popover.
What I found: focus does land on the right option (that was fixed for #9239), and the scroll is actually requested too.
useSelectableCollectionschedules it in arequestAnimationFramefrom its "scroll the focused element into view" effect. The problem is the separate unmount cleanup that cancels that frame. The repro in the issue renders inside<StrictMode>, and in StrictMode React runs every effect's cleanup once and then runs the effects again right after mount. So the cleanup cancels the pending scroll. When the scroll effect runs the second time,lastFocusedKeyalready matches the focused key anddidAutoFocusRefhas been reset, so it doesn't schedule the scroll again. The option is focused but never scrolled to.I confirmed this in Chromium (vitest browser mode) with a copy of the issue's example. With a plain
createRootrender, ArrowDown opens the list and it scrolls to "Cat". Wrapped in<StrictMode>, it stays atscrollTop0. This looks like the same cause as the StrictMode report in #9132 (rolled into #9031).The fix: the rAF callback now clears
raf.currentwhen it runs, soraf.currentis only set while a scroll is still pending. If the cleanup cancels a pending scroll, it setsdidAutoFocusRef.current = trueso the next run of the scroll effect schedules it again. This doesn't change anything outside StrictMode: on a real unmount the refs are discarded, and the keyboard and autofocus conditions for when to scroll are untouched. I chose this over adding a cleanup to the scroll effect itself, because that effect runs on every render, so it would also cancel scrolls on ordinary re-renders.Thanks to @minwookshin for confirming this on the issue: it reproduces with StrictMode on a fresh root in Chromium, Firefox and WebKit, and scrolls correctly without it. That matches what I saw. In #9239 @LFDanLu mentioned a non-StrictMode repro; I couldn't reproduce one on current
main, but if there's a case I'm missing I'm happy to dig into it. This should also cover the StrictMode part of #9031.✅ Pull Request Checklist:
AI disclosure: an AI coding assistant (Claude) helped me investigate, reproduce, and draft this change and its test. I reviewed the change before opening this PR.
No docs change is needed since this is a bug fix with no API change. I added a unit test but no story, because the existing ComboBox stories already cover this when they run under StrictMode.
📝 Test Instructions:
Automated:
packages/react-aria-components/test/ComboBox.test.js: "should scroll the first enabled option into view when opened with the keyboard".yarn testruns withSTRICT_MODE=1, so the test renders in StrictMode. It fails onmain(noscrollIntoViewcall on the option) and passes with this change.yarn lintpasses.yarn testpasses except forDateRangePicker > labeling > should have selected range description with a time, which also fails onmainfor me (a whitespace difference in the formatted time, which I think comes from my local Node 25 ICU data).Manual (any React app, or the sandbox from the issue: https://codesandbox.io/p/sandbox/cv7gyf):
ComboBoxinside<React.StrictMode>with about 10 disabled items followed by a few enabled ones, and aListBoxwithmax-height: 300px; overflow: auto.Tested: keyboard, Chromium, LTR, StrictMode and non-StrictMode. I didn't test screen readers, RTL, or touch. The changed code doesn't depend on direction or input type.
🧢 Your Project:
Personal contribution