fix: stabilize reopen ordering in basic pickers#87700
fix: stabilize reopen ordering in basic pickers#87700marufsharifi wants to merge 12 commits intoExpensify:mainfrom
Conversation
|
Hey! I see that you made changes to our Form component. Make sure to update the docs in FORMS.md accordingly. Cheers! |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
Force the wallet make-payments country selector to remount its CountrySelectionList before navigating away so the list returns at the top with the selected country already pinned in place
Restore click-driven focused-index updates for the company card country selection step so a searched country remains highlighted after it is selected, without reintroducing scroll-on-select behavior.
|
@linhvovan29546 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] |
|
@srikarparsi 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] |
|
No new product considerations - removing my assignment and unsubscribing. |
|
@marufsharifi The unit test failed. Could you please let me know the changes needed to fix these issue below? |
Restore focused-index updates in the company card country picker so a searched country stays highlighted when selected, while keeping the list from scrolling on selection.
I tracked both regressions back to the focus behavior we changed in this PR and addressed them in follow-up commits on this branch. For #87461:
For #87454 |
|
@linhvovan29546, Could you please take another look, pr is ready for your review. thanks. |
|
|
||
| return ( | ||
| <CountrySelectionList | ||
| key={countryListInstanceID} |
There was a problem hiding this comment.
Did you try other approaches? I don’t think this is a good way to fix it.
There was a problem hiding this comment.
@linhvovan29546, I looked into non-remount options, and I see three realistic approaches here:
- Reset the preserved scroll offset through the existing screen/substep lifecycle
- Instead of forcing a React remount, we can try to hook into the
Make paymentsstep re-entry and explicitly reset the list position when returning to the country step. - I think this is the cleanest non-remount direction, assuming the current step architecture gives us a reliable re-entry signal and a good place to reset the viewport.
- Hide/defer the list until the viewport is restored
- Keep the same screen instance, but avoid rendering the visible list until the selected country is pinned and the viewport is corrected.
- This would prevent the user from seeing the jump, but it adds extra complexity and feels more like masking the issue than fixing the underlying behavior.
- Restore the position imperatively with
scrollToIndex
- This works functionally, but the user can still see the list scrolling back to the top after the screen becomes visible, which is not the intended UX.
Video of the scrollToIndex behavior:
Screen.Recording.1405-01-29.at.5.26.48.PM.mov
@linhvovan29546, could you please help me decide which approach to choose?
There was a problem hiding this comment.
@marufsharifi I’m not certain about this. We can confirm with the design team.
There was a problem hiding this comment.
@marufsharifi Could you please add the recording for 1 and 2 here? so we can review
There was a problem hiding this comment.
@linhvovan29546, I will provide them asap. thanks.
There was a problem hiding this comment.
i only use this one const initialSelectedValue = useInitialSelection(selectedCountry ?? undefined, {resetOnFocus:true});
There was a problem hiding this comment.
Ah, I can still reproduce it when scroll
There was a problem hiding this comment.
@linhvovan29546, I simplified this.
Instead of coordinating the restore cycle from CountrySelection with an extra version prop, the reset now lives entirely in CountrySelectionList:
CountrySelectiononly opts into the behavior with a boolean flagCountrySelectionListuses its own focus lifecycle plus the existingSelectionListref to reset the viewport on refocus after the initial mount
That keeps the fix local to the component that owns the list behavior, removes the parent focus/version plumbing, and still avoids the key remount workaround.
There was a problem hiding this comment.
Why not use a simpler approach like this?
useLayoutEffect(() => {
if(initialSelectedValue){
selectionListRef.current?.scrollToIndex(0);
}
}, [initialSelectedValue]);
There was a problem hiding this comment.
@linhvovan29546, I kept the same direction, but added a small guard.
The raw effect was calling scrollToIndex(0) on the initial render whenever initialSelectedValue was already truthy, and that could happen before FlashList layout was ready in this flow. In practice that caused the LayoutManager is not initialized, window size is unavailable error.
So I changed it to only run when initialSelectedValue changes after mount, which keeps the behavior on return to the country step but avoids the initial-render crash.
Remove the make-payments country picker remount workaround and restore the list through the existing screen focus lifecycle instead. The wallet country list now refreshes its pinned selection snapshot on explicit re-entry and resets the viewport with the existing SelectionList ref once the selected country is back at the top. Also add focused UI coverage for the wallet restore cycle.
Replace the wallet make-payments country picker’s parent-managed restore flow with a simpler list-owned focus reset. The country step now only opts into viewport restoration, while `CountrySelectionList` handles the refocus reset through its own lifecycle and existing `SelectionList` ref. Also update the focused wallet tests to cover the simplified behavior.
Only reset the wallet country picker viewport when the pinned initial selection changes after mount. This avoids calling `scrollToIndex(0)` during the initial render, which could hit FlashList before layout is ready and trigger the LayoutManager initialization error.
Explanation of Change
Fixed Issues
$ #69184
PROPOSAL: #69184 (Comment)
Tests
Note: Below is just an example of one place this issue occurs. There are others, like the country list when adding a personal bank account to the Wallet. This PR fixes the bug on all relevant lists in the app at once.
Offline tests
Same as Tests.
QA Steps
Same as Tests.
// TODO: These must be filled out, or the issue title must include "[No QA]."
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
Record 1:
Screen.Recording.2026-03-30.at.7.24.01.PM.mov
Record 2:
Screen.Recording.2026-03-30.at.7.30.26.PM.mov
Record 3:
Screen.Recording.2026-03-30.at.7.24.55.PM.mov
Record 4:
Screen.Recording.2026-03-30.at.7.25.23.PM.mov
Record 5:
Screen.Recording.2026-03-30.at.7.26.39.PM.mov
Record 6:
Screen.Recording.2026-03-30.at.7.24.19.PM.mov
Record 8:
Screen.Recording.2026-03-30.at.7.27.10.PM.mov
Record 9:
Screen.Recording.2026-03-30.at.7.23.41.PM.mov
Record 10:
Screen.Recording.2026-04-01.at.3.13.33.PM.mov
Record 11:
Screen.Recording.2026-04-01.at.5.02.14.PM.mov
Record 12:
Screen.Recording.2026-04-01.at.5.03.25.PM.mov
Record 13:
Screen.Recording.2026-04-01.at.5.44.40.PM.mov
Record 14:
Screen.Recording.2026-04-01.at.5.45.11.PM.mov
Record 15:
Screen.Recording.2026-04-05.at.2.08.14.PM.mov
Record 16:
Screen.Recording.2026-04-05.at.2.13.16.PM.mov
Record 17:
Screen.Recording.2026-04-05.at.2.13.44.PM.mov
Record 18:
Screen.Recording.2026-04-05.at.2.14.28.PM.mov
Record 19:
Screen.Recording.2026-04-05.at.2.15.09.PM.mov
Record 20:
Screen.Recording.2026-04-05.at.2.17.19.PM.mov
Record 21:
Screen.Recording.2026-04-05.at.2.18.22.PM.mov
Record 22:
Screen.Recording.2026-04-05.at.2.22.26.PM.mov
Android: mWeb Chrome
Record 1:
Screen.Recording.2026-03-30.at.5.01.00.PM.mov
Record 2:
Screen.Recording.2026-03-30.at.5.03.08.PM.mov
Record 3:
Screen.Recording.2026-03-30.at.5.03.39.PM.mov
Record 4:
Screen.Recording.2026-03-30.at.5.13.30.PM.mov
Record 5:
Screen.Recording.2026-03-30.at.5.50.42.PM.mov
Record 6:
Screen.Recording.2026-03-30.at.4.55.18.PM.mov
Record 7:
Screen.Recording.2026-03-30.at.5.46.11.PM.mov
Record 8:
Screen.Recording.2026-03-30.at.4.54.08.PM.mov
Record 9:
Screen.Recording.2026-04-01.at.3.16.46.PM.mov
Record 10:
Screen.Recording.2026-04-01.at.3.17.57.PM.mov
Record 11:
Screen.Recording.2026-04-01.at.4.55.08.PM.mov
Record 12:
Screen.Recording.2026-04-01.at.4.57.46.PM.mov
Record 13:
Screen.Recording.2026-04-01.at.4.55.46.PM.mov
Record 14:
Screen.Recording.2026-04-01.at.4.58.45.PM.mov
Record 15:
Screen.Recording.2026-04-05.at.2.42.49.PM.mov
Record 16:
Screen.Recording.2026-04-05.at.3.02.24.PM.mov
Record 17:
Screen.Recording.2026-04-05.at.3.04.38.PM.mov
Record 18:
Screen.Recording.2026-04-05.at.3.05.12.PM.mov
Record 19:
Screen.Recording.2026-04-05.at.3.05.34.PM.mov
Record 20:
Screen.Recording.2026-04-05.at.3.06.56.PM.mov
Record 21:
Screen.Recording.2026-04-05.at.3.10.56.PM.mov
Record 22:
Screen.Recording.2026-04-05.at.3.11.45.PM.mov
Record 23:
Screen.Recording.2026-04-05.at.3.12.25.PM.mov
iOS: Native
Record 1:
Screen.Recording.2026-03-30.at.6.18.08.PM.mov
Record 2:
Screen.Recording.2026-03-30.at.6.18.31.PM.mov
Record 3:
Screen.Recording.2026-03-30.at.6.19.18.PM.mov
Record 4:
Screen.Recording.2026-03-30.at.6.20.11.PM.mov
Record 5:
Screen.Recording.2026-03-30.at.6.16.51.PM.mov
Record 6:
Screen.Recording.2026-03-30.at.6.20.53.PM.mov
Record 7:
Screen.Recording.2026-03-30.at.6.16.07.PM.mov
Record 8:
Screen.Recording.2026-04-01.at.11.33.09.AM.mov
Record 9:
Screen.Recording.2026-04-01.at.11.34.11.AM.mov
Record 10:
Screen.Recording.2026-04-01.at.11.35.50.AM.mov
Record 11:
Screen.Recording.2026-04-01.at.3.49.36.PM.mov
Record 12:
Screen.Recording.2026-04-01.at.3.53.02.PM.mov
Record 13:
Screen.Recording.2026-04-01.at.4.15.54.PM.mov
Record 14:
Screen.Recording.2026-04-01.at.4.16.47.PM.mov
Record 15:
Screen.Recording.2026-04-05.at.10.18.53.AM.mov
Record 16:
Screen.Recording.2026-04-05.at.11.00.26.AM.mov
Record 17:
Screen.Recording.2026-04-05.at.11.02.02.AM.mov
Record 18:
Screen.Recording.2026-04-05.at.11.02.53.AM.mov
Record 19:
Screen.Recording.2026-04-05.at.11.03.59.AM.mov
Record 20:
Screen.Recording.2026-04-05.at.11.05.48.AM.mov
Record 21:
Screen.Recording.2026-04-05.at.11.16.18.AM.mov
Record 22:
Screen.Recording.2026-04-05.at.12.22.30.PM.mov
iOS: mWeb Safari
Record 1:
Screen.Recording.2026-03-30.at.6.36.12.PM.mov
Record 2:
Screen.Recording.2026-03-30.at.6.36.51.PM.mov
Record 3:
Screen.Recording.2026-03-30.at.6.37.32.PM.mov
Record 4:
Screen.Recording.2026-03-30.at.6.36.33.PM.mov
Record 5:
Screen.Recording.2026-03-30.at.6.37.56.PM.mov
Record 6:
Screen.Recording.2026-03-30.at.6.38.40.PM.mov
Record 7:
Screen.Recording.2026-03-30.at.7.09.25.PM.mov
Record 8:
Screen.Recording.2026-03-30.at.7.14.01.PM.mov
Record 9:
Screen.Recording.2026-04-01.at.11.45.24.AM.mov
Record 10:
Screen.Recording.2026-04-01.at.11.46.41.AM.mov
Record 11:
Screen.Recording.2026-04-01.at.4.24.31.PM.mov
Record 12:
Screen.Recording.2026-04-01.at.4.25.32.PM.mov
Record 13:
Screen.Recording.2026-04-01.at.4.43.59.PM.mov
Record 14:
Screen.Recording.2026-04-05.at.11.53.14.AM.mov
Record 15:
Screen.Recording.2026-04-01.at.4.44.26.PM.mov
Record 16:
Screen.Recording.2026-04-05.at.11.55.14.AM.mov
Record 17:
Screen.Recording.2026-04-05.at.12.07.44.PM.mov
Record 18:
Screen.Recording.2026-04-05.at.12.08.31.PM.mov
Record 19:
Screen.Recording.2026-04-05.at.12.09.00.PM.mov
Record 20:
Screen.Recording.2026-04-05.at.12.09.16.PM.mov
Record 21:
Screen.Recording.2026-04-05.at.12.11.14.PM.mov
Record 22:
Screen.Recording.2026-04-05.at.12.13.37.PM.mov
Record 23:
Screen.Recording.2026-04-05.at.12.14.43.PM.mov
MacOS: Chrome / Safari
Record 1.
workspace.company.cards.add.card.feed.country.step.mov
Record 2.
settings.wallet.add.-.bank.account.account.detaisl.currencly.mov
Record 3.
Settings.Wallet.add.bank.account.bank.information.bank.region.mov
Record 4.
Settings.Wallet.add.bank.account.account.holder.detaisl.mov
Record 5.
create.expense.per.diem.subrate.mov
Record 6.
start.chat.roo.workspace.mov
Record 7.
start.chat.room.who.can.post.mov
Record 8.
only.available.by.route.copy.the.route.from.expensify.cards.mov
Record 9.
stat.chat.room.visibility.mov
Record 10.
expensify.card.issue.card.limit.type.mov
Record 11.
Expensify.card.card.item.Limit.Type.mov
Record 12.
create.expense.per.diem.add.subrate.then.update.that.subrate.mov
Record 13.
settings.profile.address.country.mov
Record 14.
Settings.profile.address.state.mov
Record 15.
Settings.Wallet.Add.bank.account.country.selection.mov
Record 16.
Screencast.From.2026-04-04.08-47-52.mp4
Record 17.
Screencast.From.2026-04-04.09-03-18.mp4
Record 18.
Screencast.From.2026-04-04.09-05-28.mp4
Record 19.
Screencast.From.2026-04-04.09-06-56.mp4
Record 20.
Screencast.From.2026-04-04.09-09-18.mp4
Record 21.
Screencast.From.2026-04-04.09-15-24.mp4
Record 22.
Screencast.From.2026-04-04.10-10-40.mp4
Record 23.
Screencast.From.2026-04-04.10-45-51.mp4
Record 24:
Screencast.From.2026-04-05.07-53-13.mp4