Skip to content

Move pre-selected list items to top of list throughout the app#69655

Closed
lorretheboy wants to merge 36 commits intoExpensify:mainfrom
lorretheboy:fix/69184
Closed

Move pre-selected list items to top of list throughout the app#69655
lorretheboy wants to merge 36 commits intoExpensify:mainfrom
lorretheboy:fix/69184

Conversation

@lorretheboy
Copy link
Copy Markdown
Contributor

@lorretheboy lorretheboy commented Sep 2, 2025

Explanation of Change

Fixed Issues

$ #69184
PROPOSAL: #69184 (comment)

Tests

Test 1: Country selection in Update profile address

  1. Login to ND
  2. Go to Account
  3. Scroll down and press to Address
  4. Press to select Country
  5. Note that the selected value will be on top and options don't move when select or unselect them

Test 2: Country selection in Company cards

  1. Login to ND
  2. Workspace's Company cards
  3. Add cards
  4. Note that the selected value will be on top and after that the country should remain its own position for every time user selects new value

Test 3: Country selection in Wallets

  1. Login to ND
  2. Go to Account
  3. Go to Wallet
  4. Add bank account
  5. Note that the selected value will be on top and after that the country should remain its own position for every time user selects new value

Test 4: Advanced filters

  1. Login to ND
  2. Go to Reports
  3. Click to "Filters"
  4. Test with From, To, Workspace, Category,...
    Note that the selected value will be on top and after that it should remain its own position for every time user selects new value
  • Verify that no errors appear in the JS console

Offline tests

QA Steps

Similar to Tests step

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native

https://drive.google.com/file/d/1K4Siz1poSNLtHJreaae0Y2YxFrymyvnN/view?usp=sharing

Android: mWeb Chrome

https://drive.google.com/file/d/1-mJhGIOytoh_B1kD7Gaplg4tyAfIUvQW/view?usp=sharing

iOS: Native

https://drive.google.com/file/d/1LTwiHLJHkAY0_6jDD4MaPpu8Fp3pULt0/view?usp=sharing

iOS: mWeb Safari

https://drive.google.com/file/d/181U3oF5fzUJdt61_Fow0adjbGsRUJ5Y3/view?usp=sharing

MacOS: Chrome / Safari

https://drive.google.com/file/d/1GxDmV_gK62YgAn1rU__LsghUNihYT7Tz/view?usp=sharing

MacOS: Desktop

https://drive.google.com/file/d/16XMa68hwIiIgfBZ8vm8zpKK444LHlmbU/view?usp=sharing

@melvin-bot
Copy link
Copy Markdown

melvin-bot bot commented Sep 2, 2025

Hey! I see that you made changes to our Form component. Make sure to update the docs in FORMS.md accordingly. Cheers!

);

const searchResults = searchOptions(searchValue, countries);
const searchResults = searchOptions(searchValue, countries, true);
Copy link
Copy Markdown
Contributor Author

@lorretheboy lorretheboy Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just need to pass shouldMoveSelectedToTop: true here since we auto navigate back after user selects country

@lorretheboy lorretheboy marked this pull request as ready for review September 3, 2025 11:58
@lorretheboy lorretheboy requested review from a team as code owners September 3, 2025 11:58
@melvin-bot melvin-bot bot requested review from linhvovan29546 and removed request for a team September 3, 2025 11:58
@melvin-bot
Copy link
Copy Markdown

melvin-bot bot commented Sep 3, 2025

@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]

trjExpensify
trjExpensify previously approved these changes Sep 3, 2025
Copy link
Copy Markdown
Contributor

@trjExpensify trjExpensify left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this change in Slack 👍 CC'ing @Expensify/design for a look here too!

@shawnborton
Copy link
Copy Markdown
Contributor

Big fan of this. Let's run an adhoc build when it's ready for testing?

Copy link
Copy Markdown
Contributor

@linhvovan29546 linhvovan29546 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change looks good. Could you please check some minor comments?

function CountrySelectorModal({isVisible, currentCountry, onCountrySelected, onClose, label, onBackdropPress}: CountrySelectorModalProps) {
const {translate} = useLocalize();
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
const [hasUserInteracted, setHasUserInteracted] = useState(false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use useRef instead of useState?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm looks like we can't as useRef doesn't trigger re-render. Correct me if I'm wrong @linhvovan29546

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The onCountrySelected function triggered re-render. I think it's better to use useRef instead of useState, since state updates are asynchronous and may cause unnecessary additional re-renders, while a single re-render is sufficient in this case. Additionally, calling hasUserInteracted before setCurrentCountry helps ensure the value is set correctly before other changes occur. Using a ref guarantees that the value is updated immediately and avoids potential synchronization issues.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@linhvovan29546 I have tried and no luck here, but useRef doesn't trigger re-render and also the linter is warning that we shouldn't access ref.current during render. Could you pls draft your solution for me if possible just in case I did something wrong? Thanks

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I just double-checked. I think the current useState is fine

);

const searchResults = searchOptions(debouncedSearchValue, countries);
const searchResults = searchOptions(debouncedSearchValue, countries, !hasUserInteracted);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use useMemo as well, and apply the same in other places.

* @returns An array of options with selected items at the top
*/
function moveSelectedOptionsToTop<T extends {isSelected?: boolean}>(options: T[]): T[] {
const selected: T[] = [];
Copy link
Copy Markdown
Contributor

@linhvovan29546 linhvovan29546 Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reviewing this issue, I see that we only have single selected item. So instead of using a for loop, we can use findIndex with splice like this(correct me if i'm wrong)

            const result = [...options]; // Create a shallow copy to avoid mutation
  const index = result.findIndex(option => option.isSelected);
  if (index > 0) {
    const [item] = result.splice(index, 1);
    result.unshift(item); // Move selected item to top
  }
  return result;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I think u are correct, we only have single selection

@lorretheboy
Copy link
Copy Markdown
Contributor Author

@linhvovan29546 I have resolved & replied your comments. Pls help check once u having time. Thanks

@lorretheboy
Copy link
Copy Markdown
Contributor Author

lorretheboy commented Sep 5, 2025

The failed test is not related to this PR. Re-run test...

@lorretheboy
Copy link
Copy Markdown
Contributor Author

Not related to Country selection list but should we apply the same behavior to other selections such as State selection,...
cc @trjExpensify @shawnborton @linhvovan29546

image

@shawnborton
Copy link
Copy Markdown
Contributor

Not related to Country selection list but should we apply the same behavior to other selections such as State selection,...

Yeah, I think the goal here is that all lists should behave the same way.

@dannymcclain
Copy link
Copy Markdown
Contributor

Agree!

@trjExpensify
Copy link
Copy Markdown
Contributor

Right, I agree. I'm a little worried by the question, tbh. The PR title says "Move pre-selected list items to top of list throughout the app" after all, you'd expect a code solution to be higher up than the country selector modal in a select list component or something.

@lorretheboy
Copy link
Copy Markdown
Contributor Author

Hi @trjExpensify, sorry for that question, I just want to confirm that my changes won't be redundant. I was a little bit confusing when making the PR when I read again this note in OP, though in my proposal I suggest a global changes

I will note and be more careful when reading the OP next time

image

@trjExpensify
Copy link
Copy Markdown
Contributor

Don't be sorry! Definitely encouraged and better to ask the question than not. Thanks!

@dannymcclain
Copy link
Copy Markdown
Contributor

Oh yeah I'd be into something like that Shawn!

@dubielzyk-expensify
Copy link
Copy Markdown
Contributor

dubielzyk-expensify commented Sep 29, 2025

Agree with all your comments @shawnborton 👍 The "above X" thing makes sense especially when you look at lists like Language where there's 10 items. Might make sense above 8 or something still but agree in general

@lorretheboy
Copy link
Copy Markdown
Contributor Author

lorretheboy commented Oct 2, 2025

Update: I am on the way to include the threshold for "move to the top" behavior. I took longer time than expected. I will give you all update once finish it. Thanks

@lorretheboy
Copy link
Copy Markdown
Contributor Author

Still on it. I will try to get it ready for review before Monday

@lorretheboy
Copy link
Copy Markdown
Contributor Author

Hi @linhvovan29546, I have updated the code to address new requirements.

Ps: Should we consider to hold on reviewing this issue, this I notice we would have a major refactoring here? Not

#66615

@linhvovan29546
Copy link
Copy Markdown
Contributor

I'll review this tomorrow.

@linhvovan29546
Copy link
Copy Markdown
Contributor

#66615

@lorretheboy I see that this affects the issue as well. We have updated it to use the useSearchSelector hook, right?

@lorretheboy
Copy link
Copy Markdown
Contributor Author

yes @linhvovan29546, it is also what I concerned

@shawnborton
Copy link
Copy Markdown
Contributor

What's the latest here? We now have conflicts. Let's try to get this over the finish line, we've had it open for quite a while!

@linhvovan29546
Copy link
Copy Markdown
Contributor

We have some sub-issues here: #66615, which conflict with this PR. I suggest we hold off on tis until these issues are resolved.

Screenshot 2025-10-13 at 13 53 32

@linhvovan29546
Copy link
Copy Markdown
Contributor

linhvovan29546 commented Oct 13, 2025

@lorretheboy In the meantime, I think we can also update this PR to use useSearchSelector as well

@shawnborton
Copy link
Copy Markdown
Contributor

Hmm thoughts on that @mountiny @pecanoro ?

I would really love to get this improvement in the product ASAP. If those sub-issues are going to take a long time, I would prioritize trying to get this PR finished soon.

Copy link
Copy Markdown
Contributor

@linhvovan29546 linhvovan29546 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lorretheboy I's added a few comments and am continuing to test this.

const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false;
return isPolicyExpenseChat ? getPolicyExpenseReportOption(participant, reportAttributesDerived) : getParticipantsOption(participant, personalDetails);
}),
data: [chatOptions.userToInvite]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lorretheboy How did you test this? I don't know the steps to get userToInvite

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just type a completely new email, it will suggest new user --> Press to it @linhvovan29546

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Can't select the user invite

Screen.Recording.2025-10-20.at.17.33.29.mov

const firstRenderRef = useRef(true);
const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: false});
const [invitedEmailsToAccountIDsDraft] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, {canBeMissing: true});
const initialSelectedAccountIDs = useMemo(() => new Set(invitedEmailsToAccountIDsDraft ? Object.values(invitedEmailsToAccountIDsDraft) : []), [invitedEmailsToAccountIDsDraft]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lorretheboy Bug: The 'Contacts' section is missing

Image

@lorretheboy
Copy link
Copy Markdown
Contributor Author

Hi all, for your update, I will back on this PR today. It looks like the useSearchSelector refactoring has done in some scenarios

@mountiny
Copy link
Copy Markdown
Contributor

@lorretheboy how is it looking?

@lorretheboy
Copy link
Copy Markdown
Contributor Author

@mountiny I resolved conflicts and fixing bugs now. I will update the status EOD

@lorretheboy
Copy link
Copy Markdown
Contributor Author

My original solution is not working anymore in several pages as we did refactoring to useSearchSelector. I tried to find new solution but no luck yet. Will up you all to date once I have any progress

@lorretheboy
Copy link
Copy Markdown
Contributor Author

lorretheboy commented Nov 23, 2025

Sorry all, I still couldn't come up with new solution to make it work after the selection list refactoring. I think we can reassign in case we need to do it with priority
cc @linhvovan29546 @trjExpensify

@mountiny
Copy link
Copy Markdown
Contributor

@srikarparsi seems like you are assigned to the linked issue, we might need a new proposal for this issue

@srikarparsi
Copy link
Copy Markdown
Contributor

Sounds good, let me take a deeper look at this tomorrow. Thanks for the tag @mountiny

@linhvovan29546
Copy link
Copy Markdown
Contributor

@lorretheboy Could you please close this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants