Fix/79139 - Add sticky filters to the reports page#80247
Fix/79139 - Add sticky filters to the reports page#80247JS00001 merged 13 commits intoExpensify:mainfrom
Conversation
|
@hungvu193 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] |
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.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a73135ed4
ℹ️ 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".
|
Hi @hungvu193 @JS00001, the PR has met the basic requirements, but I have a couple of questions:
Apart from these two minor details, if any other changes are needed, please feel free to let me know. |
No, lets not worry about that for now
I think that we should persist all filters, and when filters aren't valid, we just wont apply them |
Thanks for confirming. I'll make some changes based on this and let you know when it's ready. |
|
Any updates here @dmkt9? It's been a while 😄 |
| const policiesList = Object.values(allPolicies ?? {}).filter((policy): policy is NonNullable<typeof policy> => policy !== null && policy !== undefined); | ||
|
|
||
| return policiesList.some((policy) => policy.employeeList !== undefined && policy.exporter !== undefined); | ||
| return policiesList.some((policy) => policy.id !== undefined && policy.employeeList !== undefined && policy.exporter !== undefined); |
There was a problem hiding this comment.
Is this check necessary? I think when a policy has employeeList and exporter it must have policyID?
There was a problem hiding this comment.
Yes. It is necessary because in quite a few cases, when I place a log here, both employeeList and exporter are not undefined before id !== undefined. This leads to the Todos items not being rendered yet, while the items in the explore section are already rendered from the very beginning. As a result, when we open the Reports page for the first time, it may cause the wrong focused item to be recognized.
| (allSearchAdvancedFilters.current[key] as unknown) = searchAdvancedFiltersForm[key] ?? undefined; | ||
| } | ||
| allSearchAdvancedFilters.current = {...allSearchAdvancedFilters.current, type: searchAdvancedFiltersForm.type}; | ||
| prevSearchAdvancedFiltersFormsByType.current[searchAdvancedFiltersForm.type] = searchAdvancedFiltersForm; |
There was a problem hiding this comment.
Do you think that we can use usePrevious for prevSearchAdvancedFiltersFormsByType so we can reuse existing hook?
There was a problem hiding this comment.
Initially, I also intended to use usePrevious, but I encountered some issues during implementation such as:
prevSearchAdvancedFiltersFormsByTypeneeds to preserve its data, but usingusePreviousinside our hook would causeprevSearchAdvancedFiltersFormsByTypeto reset if the component gets remounted. This is most noticeable on mobile devices because the menu is displayed through a popover modal.- Storing all previous values is more convenient for us to access. If we use
usePrevious, we would need to split them into separate variables for each type, which increases complexity.
|
conflicts |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-02-01.at.16.03.32.movAndroid: mWeb ChromeScreen.Recording.2026-02-01.at.15.59.51.moviOS: HybridAppScreen.Recording.2026-02-01.at.15.46.23.moviOS: mWeb SafariScreen.Recording.2026-02-01.at.15.38.44.movMacOS: Chrome / SafariScreen.Recording.2026-02-01.at.15.33.05.mov |
JmillsExpensify
left a comment
There was a problem hiding this comment.
Big fan of this product change.
|
@dmkt9 could we remove this loading skeleton for the filters, since we're using the same filters? I think it'd feel much better (and more intuitive) if we keep the filters visible. Screen.Recording.2026-02-03.at.9.30.10.AM.mov |
src/hooks/useStickySearchFilters.ts
Outdated
| // We want to use ref here only because the value from this hook is just a derived value, | ||
| // and we don’t need to rerender when the hook’s value changes. | ||
| // eslint-disable-next-line react-hooks/refs | ||
| return shouldUpdate && isEmptyObject(allSearchAdvancedFilters.current) ? searchAdvancedFiltersForm : allSearchAdvancedFilters.current; |
There was a problem hiding this comment.
Can you explain this hook a bit more?
It seems to violate some of our react best practices. Why isnt this just a derived value? Why are we using a var outside of the hook?
There was a problem hiding this comment.
Because I noticed that the value of allSearchAdvancedFilters.current will actually only change when searchAdvancedFiltersForm changes through useOnyx. Since changes in searchAdvancedFiltersForm already trigger a re-render, I think using a ref for allSearchAdvancedFilters.current would be more efficient.
Using a variable makes sense because all the values we need to store are kept in a single variable, and this variable should preserve its value as much as possible. If we keep it inside the hook, it will be continuously reset on mobile. As for using context, we could implement it, but it adds a bit more complexity while providing equivalent effectiveness.
There was a problem hiding this comment.
but if it already triggers a re-render, then if this is a value derived from searchAdvancedFiltersForm, then it would also update in the same re-render, so this wouldn't affect the # of re-renders
There was a problem hiding this comment.
Ah, you’re right. I used useMemo instead of useEffect to ensure it always updates within the same frame
src/hooks/useStickySearchFilters.ts
Outdated
| // We want to use ref here only because the value from this hook is just a derived value, | ||
| // and we don’t need to rerender when the hook’s value changes. | ||
| // eslint-disable-next-line react-hooks/refs | ||
| return shouldUpdate && isEmptyObject(allSearchAdvancedFilters.current) ? searchAdvancedFiltersForm : allSearchAdvancedFilters.current; |
There was a problem hiding this comment.
but if it already triggers a re-render, then if this is a value derived from searchAdvancedFiltersForm, then it would also update in the same re-render, so this wouldn't affect the # of re-renders
|
@dmkt9 conflicts |
|
Thanks. It's ready for review again. |
|
🚧 @JS00001 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/JS00001 in version: 9.3.17-0 🚀
|
|
🚀 Deployed to production by https://github.com/lakchote in version: 9.3.17-9 🚀
|
Explanation of Change
This PR helps:
Expenses,Reports, andChatsitems in the Search type menu bar within the "Explore" section will be highlighted even when we change the values of the filters.Reports>Reportsdate, anduser, while on the reports pageExpensesand gets taken to the expenses viewdate, anduserReportsPagedate&userFixed Issues
$ #79139
PROPOSAL: #79139 (comment)
Tests
Same as QA Steps
Offline tests
Same as QA Steps
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand 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.native.mp4
Android: mWeb Chrome
android.mweb.mp4
iOS: Native
ios.native.mp4
iOS: mWeb Safari
ios.mweb.mp4
MacOS: Chrome / Safari
mac.safari.mp4