Skip to content

Add pagination to NewChatPage#88452

Merged
NikkiWines merged 9 commits into
Expensify:mainfrom
software-mansion-labs:fix/newChatPage/dataPreparation/pagination
Jun 10, 2026
Merged

Add pagination to NewChatPage#88452
NikkiWines merged 9 commits into
Expensify:mainfrom
software-mansion-labs:fix/newChatPage/dataPreparation/pagination

Conversation

@sharabai

@sharabai sharabai commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Note

#87881 must be merged first

Add pagination to NewChatPage (usePaginatedData)

Problem: Pagination used to live inside SelectionListWithSections. The screen prepared all personal details up front and the list just revealed more already-computed rows on scroll — purely visual, no real work saved. The screen had no control or visibility over it; you'd normally expect pagination at the data-preparation level, and the data flow in NewChatPage is complicated enough that it was easy to miss that pagination wasn't happening there at all. The new list doesn't carry this forward, so New Chat lost even the visual pagination while still processing the full list.

Approach: Move pagination up to the screen (NewChatPage) using a small, generic hook (usePaginatedData), applied at two points:

  1. Before data preparation — cap how many raw personal details are processed (real pagination).
  2. After the whole flow, before passing rows to the list, while searching — cap filtered rows (visual pagination, at the source).

Important

the two usages serve different purposes and are mutually exclusive:

  • Load flow (not searching) — slice before the heavy processing so less work is done on initial open; this is the real performance win.
  • Searching — the first pagination is skipped so search runs against the full dataset; instead, a purely visual pagination caps the filtered rows passed to the list. It works against already-processed data, so it doesn't affect performance — it's only there to keep the FlashList scroll from collapsing to something too small when the search result set is large.

Why: Pagination at the list level hid both work and intent. Owning it at the screen with a dedicated hook makes the data-prep flow traceable and controllable — on initial load (and whenever the user is not searching) "seeing pagination" now actually matches "pagination is happening."

Note

A purely visual pagination is still kept while the user is searching — search itself runs against the full dataset, but filtered rows are still fed into the list in chunks so the list doesn't mount everything at once.

Sort and dedupe before slicing (alphabetical pagination)

Problem: With pagination at the screen, page 1 was the first PAGINATION_SIZE of Object.values(personalDetails)accountID order, not alphabetical. filterAndOrderOptions then alphabetized only that arbitrary slice, so loadMore could reveal a name that sorted earlier than already-visible rows. Imported phone contacts had the same issue; an Onyx-PD/contact login overlap also wasted page slots before the late dedupe in OptionsListUtils stripped one.

Approach: Dedupe + alphabetically sort the union of Onyx personal details and imported phone contacts upstream of pagination, in a small pure helper (mergeAndSortPersonalDetailsWithContacts). Dedupe key: addSMSDomainIfPhoneNumber(login).toLowerCase(). Onyx PDs are spread first, so on collision the real-accountID copy wins over the optimistic contact copy. The late personalDetails.concat(contacts) is removed (contacts are already merged upstream). While here, orderPersonalDetailsOptions is generic-ised and its sort key aligned with personalDetailsComparator, so the upstream sort agrees with the heap re-sort inside getValidOptions.

Why: A paginated slice should match the user's mental model of "first page." Sorting + deduping upstream makes page 1 the alphabetical prefix and loadMore strictly append-only under a stable data snapshot.

Performance gains on opening New Chat

With this change, opening NewChatPage on accounts with a large contact list is noticeably faster — +75.88 ms
(+22.2%)
without the pagination.

Measured by forcing skipPagination: true on the first usePaginatedData usage (the one before data preparation) to simulate the previous behavior, and comparing against the current code with pagination enabled.

image

Fixed Issues

$ #86089
PROPOSAL:

Tests

Load-flow pagination (initial open)

  1. Open the New Chat page
  2. Verify the contacts list shows only a small slice (the scrollbar is short relative to the total number of contacts you know the account has)
  3. Scroll to the bottom of the contacts list
  4. Verify more contacts load in and the scrollbar grows, repeating as you keep scrolling

Visual pagination while searching

  1. Open the New Chat page
  2. Type a query in the search field that matches many contacts
  3. Verify results are shown and the list is capped to a small initial slice (short scrollbar again), not the full filtered set
  4. Scroll to the bottom of the search results
  5. Verify more matching contacts are revealed as you scroll
  6. Change the query (or clear it)
  7. Verify the list resets back to its first page and pagination works again from the top

Sort + dedupe correctness

  1. Open the New Chat page on an account where someone with a high accountID has an early alphabetical name (e.g. "Aaron")
  2. Verify they appear on page 1, not after scrolling
  3. Scroll to load more pages — already-visible rows must keep their order; new rows only append below
  4. If applicable: verify a person who exists in both your phone contacts and Expensify appears exactly once (with the Onyx avatar/display name)
  • Verify that no errors appear in the JS console

Offline tests

QA Steps

Same as tests

  • Verify that no errors appear in the JS console

Offline tests

QA Steps

Same as tests

  • 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
  • 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
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
pagination.mp4

@sharabai sharabai changed the title Fix/new chat page/data preparation/pagination Add pagination to NewChatPage Apr 21, 2026
@codecov

codecov Bot commented Apr 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
.../components/SelectionList/components/TextInput.tsx 97.36% <100.00%> (ø)
src/hooks/useFilteredOptions.ts 82.35% <100.00%> (ø)
src/hooks/usePaginatedData.ts 100.00% <100.00%> (ø)
src/libs/OptionsListUtils/index.ts 84.98% <100.00%> (ø)
src/libs/ReportUtils.ts 84.83% <ø> (-0.05%) ⬇️
...hatPage/mergeAndSortPersonalDetailsWithContacts.ts 100.00% <100.00%> (ø)
...es/NewChatPage/useGroupChatDraftParticipantSync.ts 100.00% <100.00%> (ø)
src/pages/NewChatPage/index.tsx 78.01% <76.92%> (-0.78%) ⬇️
... and 42 files with indirect coverage changes

@sharabai sharabai force-pushed the fix/newChatPage/dataPreparation/pagination branch 2 times, most recently from 32a8f31 to f020be6 Compare April 22, 2026 13:40
Comment on lines +35 to +41
if (skipPagination) {
return {paginatedData: data, loadMore: () => {}, hasMore: false};
}

if (pageSize < 1) {
return {paginatedData: [], loadMore: () => {}, hasMore: 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.

Could these early returns can go above the if (resetKey !== prevResetKey) condition? 🤔

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.

at first, after reading this question, I though it would be better to move it down. But actually it needs to stay in place.

Example:

usePaginatedData(data, pageSize, { resetKey: "false", skipPagination:false})
// some time later
usePaginatedData(data, pageSize, { resetKey: "true", skipPagination:true})
// some time later 
usePaginatedData(data, pageSize, { resetKey: "false", skipPagination:false})

If we move the condition down, the change in resetKey would go unnoticed; the pagination would not reset to the first page.

Comment thread src/pages/NewChatPage/index.tsx Outdated
paginatedData: personalDetails,
loadMore: loadMorePersonalDetails,
hasMore: hasMorePersonalDetails,
} = usePaginatedData(allPersonalDetailOptions, PAGINATION_SIZE, {skipPagination: isSearching, resetKey: String(isSearching)});

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.

If isSearching return a boolean so doesn't String(isSearching) return "true" or "false"? I think it might be a mistake

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.

That's an area I was also unsure about. It looks a little bit off, and I'm open to suggestions how to do it better.

Returning to your question, I intentionally type casted boolean to get "true"/"false" as the resetKey.

What case am I trying to handle here?

  1. Initial load -> use this pagination
  2. User starts typing in search field -> skip pagination in this place
  3. User clears search query -> pagination is back, but page is reset

isSearching is a perfect flag for when we should reset pagination, but since key is typed as a string, I casted the boolean value here.

I tried to do something similar to React Component key, which is defined as:

interface Attributes {
    key?: Key | null | undefined;
}

type Key =
      | string
      | number
      | bigint

It does not accept a boolean value, and it makes sense, especially here with the name resetKey, which is a key that on change would cause a reset to internal state. If it also accepted a boolean value, it becomes a bit hard to understand the intent of the prop. What does resetKey: true mean? So I'd stick with a string type.

Ideally I'd change the key only once when we go back from isSearching: true -> false. And it needs to a string value for reasons explained above.
The simple String(isSearching) does it well, wouldn't cause unnecessary re-renders when the searchTerm is changing like a -> ab -> abc -> abcd.

The casting may look strange, but it does what it set out to do, causes only one additional re-render vs potentially many more by passing searchTerm

It could always be handled more explicitly (we can write out explicit logic to track changes between renders to isSearching and change the key only on the flip from true to false), but imo String(isSearching) is enough.

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.

@staszekscp I agree with your point that it might be confusing, so I used a more explicite string key here, so that next devs would not have to wonder if this was done intentionally.
Now is smth like: isSearching ? 'OneKey' : 'AnotherKey'

import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import type SelectedOption from './types';

function useGroupDraftRestore(

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.

I wonder if this hook should be renamed of have at least some comment to explain what it does, because it doesn't seem intuitive to me 😄

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.

Yeah, totally makes sense! This was also flagged by a reviewer on a previous PR that the current one is based from.
Now that I rebased to only include the relevant commits to this PR, js doc is in place.

Comment on lines +23 to +24
const shouldRestoreSelectedOptionsRef = useRef(true);
const isScreenInBackgroundRef = useRef(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.

Why are these values treated as refs?

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.

To avoid causing re-renders.
They control the returned value from the selector. And the selector itself causes re-renders. But we normally don't need those values (only in the few exceptions). Normally, selector's supposed to return undefined (nothing) and not react to changes in the draft from Onyx.

Comment on lines +28 to +31
if (!isSubscriptionActive) {
return undefined;
}
return draft?.participants;

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.

Wouldn't eg. empty array be more natural here so we can avoid nullish coalescing in its usage?

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.

Tried applying this change, but the field returned first in ternary is itself possibly undefined, so unfortunately it didn't help.
image

Even if the selector always returns an array (guarded with additional nullish coalescing operator),

image

the value returned from onyx is still possibly undefined.

image

Comment on lines +77 to +80
isScreenInBackgroundRef.current = false;

return () => {
isScreenInBackgroundRef.current = true;

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.

Isn't there any utility function from Navigation to check if the screen is backgrounded? 🤔

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.

Great catch!
The useIsFocused, but it causes re-renders. I need a ref here, but even then, there's a hook useIsFocusedRef, which I myself refactored out from some component some time ago because I needed it.
Thanks, I'll be able to shorten boilerplate code here.

@sharabai sharabai force-pushed the fix/newChatPage/dataPreparation/pagination branch 2 times, most recently from 68fe534 to 9e86bb0 Compare April 23, 2026 11:34
@sharabai sharabai requested a review from staszekscp April 23, 2026 16:27
@sharabai sharabai force-pushed the fix/newChatPage/dataPreparation/pagination branch from c15ac76 to d1abdd0 Compare April 23, 2026 16:33
@sharabai

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1abdd0b1f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

Comment on lines +29 to +32

if (resetKey !== prevResetKey) {
setPrevResetKey(resetKey);
setCurrentPage(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reset pagination state before slicing data

When resetKey changes after the user has already loaded multiple pages, this hook queues setCurrentPage(1) but still computes limit from the previous currentPage in the same render. That means one render returns too many rows for the new context (for example, after changing the search term), which can briefly show stale pagination and trigger extra onEndReached work before the next render corrects it.

Useful? React with 👍 / 👎.

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.

When you call the set function during render, React will re-render that component immediately after your component exits with a return statement, and before rendering the children.

@sharabai

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

@sharabai sharabai force-pushed the fix/newChatPage/dataPreparation/pagination branch 2 times, most recently from bebd537 to c00fd1e Compare April 27, 2026 11:58
@sharabai

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

reports,
personalDetails: personalDetails.concat(contacts),
},

P1 Badge Move contact pagination after ordering

Paginating allPersonalDetailOptions before calling getValidOptions means we sort and filter only an arbitrary raw slice (Object.values order) instead of the full contact set, so the first page can exclude contacts that should be at the top (e.g., alphabetically) and then reorder/jump when more pages are loaded. getValidOptions/filterAndOrderOptions are where personal details are actually ordered, so slicing needs to happen after those steps to preserve stable, correct list ordering.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

@sharabai sharabai force-pushed the fix/newChatPage/dataPreparation/pagination branch from c00fd1e to 0a2cfc1 Compare April 28, 2026 14:27
@sharabai

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

sort alphabetically before paginating

add tests
@sharabai sharabai force-pushed the fix/newChatPage/dataPreparation/pagination branch from 0a2cfc1 to 6d23e93 Compare April 29, 2026 13:15
@sharabai

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

@sharabai sharabai marked this pull request as ready for review April 29, 2026 13:54
@sharabai sharabai requested review from a team as code owners April 29, 2026 13:54
@melvin-bot melvin-bot Bot requested a review from dukenv0307 April 29, 2026 13:54
@sharabai

Copy link
Copy Markdown
Contributor Author

@NikkiWines
I didn't change much the utils from the the PR you mentioned. I’m happy to defer to your judgment here since you have more context 👍

@NikkiWines

Copy link
Copy Markdown
Contributor

Yeah, i think there's some potential for conflicts if we reimplement the changes in the linked PR - just want to make sure nothing overlaps before we move forward with this one 🙇

@NikkiWines

Copy link
Copy Markdown
Contributor

Confirmed we're keeping this on hold for now

@NikkiWines

Copy link
Copy Markdown
Contributor

Still on hold pending this

@NikkiWines

Copy link
Copy Markdown
Contributor

helloooo 👋 sorry for the delay, but it looks like we can now move forward with this PR 🙇

@NikkiWines NikkiWines changed the title [HOLD] Add pagination to NewChatPage Add pagination to NewChatPage May 29, 2026
@sharabai

sharabai commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

@staszekscp, @NikkiWines, @dukenv0307 PR is ready. Conflicts've been resolved.

@dukenv0307

Copy link
Copy Markdown
Contributor

Looks good to me

Screen.Recording.2026-06-02.at.10.13.19.mov

@staszekscp staszekscp left a comment

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.

Looking good! Good job!

Comment thread src/pages/NewChatPage/index.tsx Outdated

const handleEndReached = () => {
if (!hasMore || !areOptionsInitialized || !isScreenFocusedRef.current) {
if ((!hasMoreFilteredPersonalDetails && !hasMorePersonalDetails && !hasMore) || !areOptionsInitialized || !isScreenFocusedRef.current) {

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.

NAB: I had to think for a moment to understand this condition 😄 Maaaaybe this could become a separate hasMoreData variable instead of (!hasMoreFilteredPersonalDetails && !hasMorePersonalDetails && !hasMore)

@sharabai sharabai Jun 3, 2026

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.

@staszekscp yep, it took me a moment to remember what this condition checked too 😅. Which is a good indicator that changing it would help.

@NikkiWines NikkiWines left a comment

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.

looks good, couple very small things

Comment thread src/hooks/useFilteredOptions.ts Outdated
Comment thread src/hooks/usePaginatedData.ts
Comment thread src/pages/NewChatPage/index.tsx Outdated
@NikkiWines

Copy link
Copy Markdown
Contributor

@sharabai friendly bump here 🙇

@staszekscp

Copy link
Copy Markdown
Contributor

Hey @NikkiWines! On behalf of @sharabai I wanted to let you know that Sergei is on sick leave and will handle the review after he's back in shape! Also, sorry for not responding since Wednesday, but Thursday and Friday there was a bank holiday in Poland and we all were OOO 😄

@NikkiWines

Copy link
Copy Markdown
Contributor

No worries, thanks for the update @staszekscp 🙇 Hope you get well soon @sharabai, and I hope you all had a good holiday weekend

@sharabai

Copy link
Copy Markdown
Contributor Author

@NikkiWines thanks for the suggestions! Looks cleaner with them.

@NikkiWines NikkiWines left a comment

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.

:shipit:

@NikkiWines NikkiWines merged commit 7f08aa6 into Expensify:main Jun 10, 2026
32 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🚧 @NikkiWines has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/NikkiWines in version: 9.4.5-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

@MelvinBot

Copy link
Copy Markdown
Contributor

Help site review — no docs changes required

I reviewed the changes in this PR against the help articles under App/docs/articles, and no help site updates are needed. I did not create a draft docs PR.

Why: This PR is an internal performance/architecture refactor of how NewChatPage prepares its contact data — it moves pagination up to the screen (usePaginatedData), and sorts/dedupes personal details + imported contacts before slicing. None of this changes the user-facing workflow documented in the help site:

  • The relevant article, Create a New Chat, describes the steps to start a private/group/room chat (click +Start Chat → enter name/email/phone → select). Those steps are unchanged.
  • The changes (load-flow pagination, visual pagination while searching, alphabetical sort/dedupe) are about how fast and in what order contacts are processed/revealed — implementation details, not documented behavior. The help site does not describe contact-list pagination or sort order, so there is nothing to update or correct.
  • No new features, settings, tabs, buttons, or labels were introduced.
Files reviewed

All 12 changed files are TypeScript source/tests (usePaginatedData.ts, NewChatPage/index.tsx, mergeAndSortPersonalDetailsWithContacts.ts, OptionsListUtils, ReportUtils, hooks, and unit tests). None are under docs/articles, and none alter a customer-facing flow.

If you believe a customer-facing behavior did change in a way that should be documented, let me know what the user would observe and I'll draft the docs update.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/Julesssss in version: 9.4.5-6 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

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.

6 participants