|
1 | 1 | import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; |
2 | 2 | import {useOnyx} from 'react-native-onyx'; |
3 | 3 | import usePrevious from '@hooks/usePrevious'; |
4 | | -import {createOptionFromReport, createOptionList} from '@libs/OptionsListUtils'; |
| 4 | +import {createOptionFromReport, createOptionList, processReport} from '@libs/OptionsListUtils'; |
5 | 5 | import type {OptionList, SearchOption} from '@libs/OptionsListUtils'; |
6 | 6 | import {isSelfDM} from '@libs/ReportUtils'; |
7 | 7 | import ONYXKEYS from '@src/ONYXKEYS'; |
@@ -46,34 +46,80 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { |
46 | 46 | reports: [], |
47 | 47 | personalDetails: [], |
48 | 48 | }); |
49 | | - const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {canBeMissing: true}); |
50 | | - const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); |
51 | | - |
| 49 | + const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true}); |
| 50 | + const prevReportAttributesLocale = usePrevious(reportAttributes?.locale); |
| 51 | + const [reports, {sourceValue: changedReports}] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); |
52 | 52 | const personalDetails = usePersonalDetails(); |
53 | 53 | const prevPersonalDetails = usePrevious(personalDetails); |
| 54 | + const hasInitialData = useMemo(() => Object.keys(personalDetails ?? {}).length > 0, [personalDetails]); |
| 55 | + |
| 56 | + const loadOptions = useCallback(() => { |
| 57 | + const optionLists = createOptionList(personalDetails, reports); |
| 58 | + setOptions({ |
| 59 | + reports: optionLists.reports, |
| 60 | + personalDetails: optionLists.personalDetails, |
| 61 | + }); |
| 62 | + }, [personalDetails, reports]); |
54 | 63 |
|
55 | 64 | /** |
56 | | - * This effect is used to update the options list when reports change. |
| 65 | + * This effect is responsible for generating the options list when their data is not yet initialized |
57 | 66 | */ |
58 | 67 | useEffect(() => { |
59 | | - // there is no need to update the options if the options are not initialized |
60 | | - if (!areOptionsInitialized.current || !reports) { |
| 68 | + if (!areOptionsInitialized.current || !reports || hasInitialData) { |
| 69 | + return; |
| 70 | + } |
| 71 | + |
| 72 | + loadOptions(); |
| 73 | + }, [reports, personalDetails, hasInitialData, loadOptions]); |
| 74 | + |
| 75 | + /** |
| 76 | + * This effect is responsible for generating the options list when the locale changes |
| 77 | + * Since options might use report attributes, it's necessary to call this after report attributes are loaded with the new locale to make sure the options are generated in a proper language |
| 78 | + */ |
| 79 | + useEffect(() => { |
| 80 | + if (reportAttributes?.locale === prevReportAttributesLocale) { |
| 81 | + return; |
| 82 | + } |
| 83 | + |
| 84 | + loadOptions(); |
| 85 | + }, [prevReportAttributesLocale, loadOptions, reportAttributes?.locale]); |
| 86 | + |
| 87 | + /** |
| 88 | + * This effect is responsible for updating the options only for changed reports |
| 89 | + */ |
| 90 | + useEffect(() => { |
| 91 | + if (!changedReports || !areOptionsInitialized.current) { |
61 | 92 | return; |
62 | 93 | } |
63 | | - // Since reports updates can happen in bulk, and some reports depend on other reports, we need to recreate the whole list from scratch. |
64 | | - const newReports = createOptionList(personalDetails, reports).reports; |
65 | 94 |
|
66 | 95 | setOptions((prevOptions) => { |
67 | | - const newOptions = { |
| 96 | + const changedReportEntries = Object.values(changedReports); |
| 97 | + if (changedReportEntries.length === 0) { |
| 98 | + return prevOptions; |
| 99 | + } |
| 100 | + |
| 101 | + const updatedReportsMap = new Map(prevOptions.reports.map((report) => [report.reportID, report])); |
| 102 | + changedReportEntries.forEach((report) => { |
| 103 | + if (!report) { |
| 104 | + return; |
| 105 | + } |
| 106 | + |
| 107 | + const reportID = report?.reportID; |
| 108 | + const {reportOption} = processReport(report, personalDetails); |
| 109 | + |
| 110 | + if (reportOption) { |
| 111 | + updatedReportsMap.set(reportID, reportOption); |
| 112 | + } else { |
| 113 | + updatedReportsMap.delete(reportID); |
| 114 | + } |
| 115 | + }); |
| 116 | + |
| 117 | + return { |
68 | 118 | ...prevOptions, |
69 | | - reports: newReports, |
| 119 | + reports: Array.from(updatedReportsMap.values()), |
70 | 120 | }; |
71 | | - |
72 | | - return newOptions; |
73 | 121 | }); |
74 | | - |
75 | | - // eslint-disable-next-line react-compiler/react-compiler |
76 | | - }, [reports, personalDetails, preferredLocale]); |
| 122 | + }, [changedReports, personalDetails]); |
77 | 123 |
|
78 | 124 | /** |
79 | 125 | * This effect is used to update the options list when personal details change. |
@@ -142,14 +188,6 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { |
142 | 188 | // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps |
143 | 189 | }, [personalDetails]); |
144 | 190 |
|
145 | | - const loadOptions = useCallback(() => { |
146 | | - const optionLists = createOptionList(personalDetails, reports); |
147 | | - setOptions({ |
148 | | - reports: optionLists.reports, |
149 | | - personalDetails: optionLists.personalDetails, |
150 | | - }); |
151 | | - }, [personalDetails, reports]); |
152 | | - |
153 | 191 | const initializeOptions = useCallback(() => { |
154 | 192 | loadOptions(); |
155 | 193 | areOptionsInitialized.current = true; |
@@ -182,7 +220,7 @@ const useOptionsListContext = () => useContext(OptionsListContext); |
182 | 220 | const useOptionsList = (options?: {shouldInitialize: boolean}) => { |
183 | 221 | const {shouldInitialize = true} = options ?? {}; |
184 | 222 | const {initializeOptions, options: optionsList, areOptionsInitialized, resetOptions} = useOptionsListContext(); |
185 | | - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: true}); |
| 223 | + const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: false}); |
186 | 224 |
|
187 | 225 | useEffect(() => { |
188 | 226 | if (!shouldInitialize || areOptionsInitialized || isLoadingApp) { |
|
0 commit comments