Web - Expense - Expense RHPs in the background flicker when opening Category picker#87070
Conversation
| @@ -74,6 +78,8 @@ function CategoryPicker({selectedCategory, policyID, onSubmit, addBottomSafeArea | |||
| onChangeText: setSearchValue, | |||
| headerMessage: getHeaderMessageForNonUserList(categoryData.length > 0, debouncedSearchValue), | |||
| hint: offlineMessage, | |||
| disableAutoFocus: true, | |||
| ref: inputCallbackRef as unknown as RefObject<BaseTextInputRef | null>, | |||
There was a problem hiding this comment.
NAB: I'd rather change type in src/components/SelectionList/types.ts to avoid casting
There was a problem hiding this comment.
To avoid the as unknown as RefObject<BaseTextInputRef | null> cast, change the ref type in TextInputOptions (in src/components/SelectionList/types.ts:267) from:
ref?: RefObject<BaseTextInputRef | null>;to:
ref?: Ref<BaseTextInputRef | null>;Ref<T> (from React) is RefCallback<T> | RefObject<T> | null, so it accepts both callback refs and object refs. This works because mergeRefs in TextInput.tsx:102 already accepts Ref<T>.
However, there's still a secondary type mismatch: useAutoFocusInput returns a callback typed as (ref: TextInput | null) => void, but TextInputOptions.ref expects BaseTextInputRef. Since BaseTextInputRef = HTMLFormElement | AnimatedTextInputRef and AnimatedTextInputRef extends TextInput, TextInput is structurally compatible with part of this union. You'd still need a minor cast like inputCallbackRef as (ref: BaseTextInputRef | null) => void, or alternatively widen the TextInputOptions.ref type to Ref<TextInput | null> (since that's what BaseTextInput ultimately wraps). The cleanest option depends on how broadly you want to accept refs in the SelectionList API.
WojtekBoman — should I implement this change? If so, please add me as an assignee and I'll push a commit.
babec2f to
a15be50
Compare
|
@QichenZhu Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
Can we also fix Tag picker? Screen.Recording.2026-04-03.at.12.43.48.pm.mov |
@aimane-chnaif |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppios.moviOS: mWeb Safarimsafari.movMacOS: Chrome / Safariweb.mov |
|
🤖 Code Review The The remaining cast Overall this looks clean and minimal. No other concerns. |
|
Codex Review: Didn't find any major issues. 🎉 ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
joekaufmanexpensify
left a comment
There was a problem hiding this comment.
Good for product
|
🚧 @chiragsalian has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/chiragsalian in version: 9.3.53-0 🚀
Bundle Size Analysis (Sentry): |
|
No help site changes are required for this PR. The changes are purely internal UI fixes that modify focus timing behavior in
These changes don't alter any user-facing feature names, workflows, settings labels, or documented functionality — they only fix a visual glitch with search input focus. |
|
🚀 Deployed to staging by https://github.com/chiragsalian in version: 9.3.53-0 🚀
Bundle Size Analysis (Sentry): |
|
🚀 Deployed to production by https://github.com/roryabraham in version: 9.3.53-7 🚀
|
Replace the `InteractionManager.runAfterInteractions(() => ref.focus())` pattern in SplitListItem, DatePicker, and IOURequestEditReportCommon with the canonical `useAutoFocusInput` hook. The raw pattern has no cancellation mechanism, so a deferred focus callback can survive the dismissing screen and fire during the next RHP's slide-in animation, blocking frames and causing the sluggish animation reported in Expensify#87174. useAutoFocusInput wraps the same call in a useEffect whose cleanup calls focusTaskHandle.cancel(), and a useFocusEffect that resets the screen-transition-ended flag on unfocus — together these cancel any pending focus task when the screen starts closing. Aligns these three outlier files with the same pattern applied in Expensify#87070 and the 100+ existing useAutoFocusInput usages elsewhere. Fixes Expensify#87174
Explanation of Change
Fixed flickering/instability of the search input focus in
CategoryPickerduring page transitions. Instead of usingautoFocus(which fires immediately and conflicts with navigation animations), the component now uses theuseAutoFocusInputhook withdisableAutoFocus: trueon theSelectionList, deferring focus until the transition settles. Also updatedTextInputOptions.reftype fromRefObjecttoRefto support callback refs.Fixed Issues
$ #87027
PROPOSAL:
Tests
Open the app and navigate to an expense or money request that has a category field.
Tap the category field to open the
CategoryPickerscreen.Verify that the search input focuses smoothly without flickering or jumping after the page transition animation completes.
With the category picker open, type a search query in the search input.
Verify that filtering works correctly and results update as expected.
Navigate away from the category picker and re-open it.
Verify that the search input re-focuses correctly on each open without any visual glitch or focus flicker.
Offline tests
N/A — this change only affects focus timing behavior during page transitions and does not involve API calls, data fetching, or Onyx operations.
QA Steps
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2026-04-03.at.13.32.02.mov