-
Notifications
You must be signed in to change notification settings - Fork 4k
refactor: remove todos derived value #94677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mountiny
merged 23 commits into
Expensify:main
from
callstack-internal:perf/remove-todos-derived-value
Jun 29, 2026
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
edc4956
Decouple NewReportWorkspaceSelection from TODOS derived value
TMisiukiewicz 94ca90e
Extract createTodosReportsAndTransactions into shared TodosUtils
TMisiukiewicz 7aa0ee6
Add useTodoCounts hook and migrate count consumers off TODOS derived …
TMisiukiewicz ffbd138
Add useTodoSearchResults hook for single-category live to-do data
TMisiukiewicz 7417fce
Switch SearchResultsProvider to useTodoSearchResults and remove useTodos
TMisiukiewicz 34b437c
Merge remote-tracking branch 'origin/main' into perf/remove-todos-der…
TMisiukiewicz ef0b379
Remove unused TODOS derived value
TMisiukiewicz 1c4c7bc
Freeze useTodoCounts when consumer is disabled
TMisiukiewicz 16e5709
Freeze useTodoCounts in ForYouSection when home is unfocused
TMisiukiewicz e681dbb
Add useTodoCounts tests and fix TODOS derived-value removal fallout
TMisiukiewicz 9aa3af6
code cleanup
TMisiukiewicz 6474ce9
Merge remote-tracking branch 'origin/main' into perf/remove-todos-der…
TMisiukiewicz 82ebb7a
Merge remote-tracking branch 'origin/main' into perf/remove-todos-der…
TMisiukiewicz 76b5ea8
fix knip
TMisiukiewicz 28ef04f
Merge remote-tracking branch 'origin/main' into perf/remove-todos-der…
TMisiukiewicz d045b14
Extract shared reportMatchesTodoBucket helper to dedupe todo classifi…
TMisiukiewicz a9d81bf
Treat empty todo search bucket as no results
TMisiukiewicz e68066f
Merge remote-tracking branch 'origin/main' into perf/remove-todos-der…
TMisiukiewicz 4aa8a23
Merge branch 'main' into perf/remove-todos-derived-value
TMisiukiewicz 9c9c1ba
remove unused EMPTY_TODOS_REPORT_COUNTS
TMisiukiewicz d795a89
add todos docs and tests
TMisiukiewicz ae9c537
fix lint in TodosUtilsTest
TMisiukiewicz 7a7fe10
Merge remote-tracking branch 'origin/main' into perf/remove-todos-der…
TMisiukiewicz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import {useState} from 'react'; | ||
| // We need direct access to useOnyx from react-native-onyx to avoid reading search snapshots instead of live to-do data | ||
| // eslint-disable-next-line no-restricted-imports | ||
| import {useOnyx} from 'react-native-onyx'; | ||
| import createTodosReportsAndTransactions from '@libs/TodosUtils'; | ||
| import CONST from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
|
|
||
| type TodoCounts = { | ||
| [CONST.SEARCH.SEARCH_KEYS.SUBMIT]: number; | ||
| [CONST.SEARCH.SEARCH_KEYS.APPROVE]: number; | ||
| [CONST.SEARCH.SEARCH_KEYS.PAY]: number; | ||
| [CONST.SEARCH.SEARCH_KEYS.EXPORT]: number; | ||
| }; | ||
|
|
||
| type TodoSingleReportIDs = { | ||
| [CONST.SEARCH.SEARCH_KEYS.SUBMIT]: string | undefined; | ||
| [CONST.SEARCH.SEARCH_KEYS.APPROVE]: string | undefined; | ||
| [CONST.SEARCH.SEARCH_KEYS.PAY]: string | undefined; | ||
| [CONST.SEARCH.SEARCH_KEYS.EXPORT]: string | undefined; | ||
| }; | ||
|
|
||
| const TODO_KEYS = [CONST.SEARCH.SEARCH_KEYS.SUBMIT, CONST.SEARCH.SEARCH_KEYS.APPROVE, CONST.SEARCH.SEARCH_KEYS.PAY, CONST.SEARCH.SEARCH_KEYS.EXPORT] as const; | ||
|
|
||
| /** | ||
| * Computes live to-do report counts and, for each bucket that contains exactly one report, that report's ID. | ||
| * Runs the to-do classification on demand from live Onyx data, only while a consumer is mounted, replacing the | ||
| * always-on TODOS derived value for count consumers. | ||
| * | ||
| * Pass `enabled: false` to freeze the hook: while disabled it skips the expensive classification and returns the | ||
| * last computed result, so e.g. an unfocused screen stops recomputing to-do counts on background Onyx writes. | ||
| */ | ||
| function useTodoCounts(enabled = true): {counts: TodoCounts; singleReportIDs: TodoSingleReportIDs} { | ||
| const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); | ||
| const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); | ||
| const [allReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS); | ||
| const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); | ||
| const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); | ||
| const [allReportMetadata] = useOnyx(ONYXKEYS.COLLECTION.REPORT_METADATA); | ||
| const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); | ||
| const [session] = useOnyx(ONYXKEYS.SESSION); | ||
| const [personalDetailsList] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); | ||
|
|
||
| // Holds the most recent result so a frozen (inactive) consumer can keep returning it without recomputing. | ||
| const [frozen, setFrozen] = useState<{counts: TodoCounts; singleReportIDs: TodoSingleReportIDs} | null>(null); | ||
|
|
||
| // While frozen, reuse the last captured result and skip the expensive classification below. | ||
| if (!enabled && frozen) { | ||
| return frozen; | ||
| } | ||
|
|
||
| const userAccountID = session?.accountID ?? CONST.DEFAULT_NUMBER_ID; | ||
| const login = personalDetailsList?.[userAccountID]?.login ?? session?.email ?? ''; | ||
|
|
||
| const {reportsToSubmit, reportsToApprove, reportsToPay, reportsToExport} = createTodosReportsAndTransactions({ | ||
| allReports, | ||
| allTransactions, | ||
| allPolicies, | ||
| allReportNameValuePairs, | ||
| allReportActions, | ||
| allReportMetadata, | ||
| personalDetailsList, | ||
| bankAccountList, | ||
| currentUserAccountID: userAccountID, | ||
| login, | ||
| }); | ||
|
|
||
| const counts: TodoCounts = { | ||
| [CONST.SEARCH.SEARCH_KEYS.SUBMIT]: reportsToSubmit.length, | ||
| [CONST.SEARCH.SEARCH_KEYS.APPROVE]: reportsToApprove.length, | ||
| [CONST.SEARCH.SEARCH_KEYS.PAY]: reportsToPay.length, | ||
| [CONST.SEARCH.SEARCH_KEYS.EXPORT]: reportsToExport.length, | ||
| }; | ||
|
|
||
| const singleReportIDs: TodoSingleReportIDs = { | ||
| [CONST.SEARCH.SEARCH_KEYS.SUBMIT]: reportsToSubmit.length === 1 ? reportsToSubmit.at(0)?.reportID : undefined, | ||
| [CONST.SEARCH.SEARCH_KEYS.APPROVE]: reportsToApprove.length === 1 ? reportsToApprove.at(0)?.reportID : undefined, | ||
| [CONST.SEARCH.SEARCH_KEYS.PAY]: reportsToPay.length === 1 ? reportsToPay.at(0)?.reportID : undefined, | ||
| [CONST.SEARCH.SEARCH_KEYS.EXPORT]: reportsToExport.length === 1 ? reportsToExport.at(0)?.reportID : undefined, | ||
| }; | ||
|
|
||
| const value = {counts, singleReportIDs}; | ||
|
|
||
| // Capture the latest result so it can be returned verbatim once the consumer freezes. Only re-store when a | ||
| // count or single-report ID actually changes, so the setState-during-render can't loop. | ||
| const hasChanged = !frozen || TODO_KEYS.some((key) => frozen.counts[key] !== counts[key] || frozen.singleReportIDs[key] !== singleReportIDs[key]); | ||
| if (hasChanged) { | ||
| setFrozen(value); | ||
| } | ||
|
|
||
| return value; | ||
| } | ||
|
|
||
| export default useTodoCounts; | ||
| export type {TodoCounts}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.