Skip to content

Commit

Permalink
Merge pull request #38207 from callstack-internal/perf/cache-options
Browse files Browse the repository at this point in the history
perf: Cache search options
  • Loading branch information
grgia authored Apr 3, 2024
2 parents 879378f + 50ed5e4 commit 398636a
Show file tree
Hide file tree
Showing 18 changed files with 685 additions and 408 deletions.
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import HTMLEngineProvider from './components/HTMLEngineProvider';
import InitialURLContextProvider from './components/InitialURLContextProvider';
import {LocaleContextProvider} from './components/LocaleContextProvider';
import OnyxProvider from './components/OnyxProvider';
import OptionsListContextProvider from './components/OptionListContextProvider';
import PopoverContextProvider from './components/PopoverProvider';
import SafeArea from './components/SafeArea';
import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider';
Expand Down Expand Up @@ -82,6 +83,7 @@ function App({url}: AppProps) {
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
OptionsListContextProvider,
]}
>
<CustomStatusBarAndBackground />
Expand Down
4 changes: 2 additions & 2 deletions src/components/CategoryPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC
const [sections, headerMessage, shouldShowTextInput] = useMemo(() => {
const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter((p) => !isEmptyObject(p));
const {categoryOptions} = OptionsListUtils.getFilteredOptions(
{},
{},
[],
[],
[],
debouncedSearchValue,
selectedOptions,
Expand Down
142 changes: 142 additions & 0 deletions src/components/OptionListContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import {withOnyx} from 'react-native-onyx';
import type {OnyxCollection} from 'react-native-onyx';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import type {OptionList} from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Report} from '@src/types/onyx';
import {usePersonalDetails} from './OnyxProvider';

type OptionsListContextProps = {
/** List of options for reports and personal details */
options: OptionList;
/** Function to initialize the options */
initializeOptions: () => void;
/** Flag to check if the options are initialized */
areOptionsInitialized: boolean;
};

type OptionsListProviderOnyxProps = {
/** Collection of reports */
reports: OnyxCollection<Report>;
};

type OptionsListProviderProps = OptionsListProviderOnyxProps & {
/** Actual content wrapped by this component */
children: React.ReactNode;
};

const OptionsListContext = createContext<OptionsListContextProps>({
options: {
reports: [],
personalDetails: [],
},
initializeOptions: () => {},
areOptionsInitialized: false,
});

function OptionsListContextProvider({reports, children}: OptionsListProviderProps) {
const areOptionsInitialized = useRef(false);
const [options, setOptions] = useState<OptionList>({
reports: [],
personalDetails: [],
});
const personalDetails = usePersonalDetails();

useEffect(() => {
// there is no need to update the options if the options are not initialized
if (!areOptionsInitialized.current) {
return;
}

const lastUpdatedReport = ReportUtils.getLastUpdatedReport();

if (!lastUpdatedReport) {
return;
}

const newOption = OptionsListUtils.createOptionFromReport(lastUpdatedReport, personalDetails);
const replaceIndex = options.reports.findIndex((option) => option.reportID === lastUpdatedReport.reportID);

if (replaceIndex === -1) {
return;
}

setOptions((prevOptions) => {
const newOptions = {...prevOptions};
newOptions.reports[replaceIndex] = newOption;
return newOptions;
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reports]);

useEffect(() => {
// there is no need to update the options if the options are not initialized
if (!areOptionsInitialized.current) {
return;
}

// since personal details are not a collection, we need to recreate the whole list from scratch
const newPersonalDetailsOptions = OptionsListUtils.createOptionList(personalDetails).personalDetails;

setOptions((prevOptions) => {
const newOptions = {...prevOptions};
newOptions.personalDetails = newPersonalDetailsOptions;
return newOptions;
});
}, [personalDetails]);

const loadOptions = useCallback(() => {
const optionLists = OptionsListUtils.createOptionList(personalDetails, reports);
setOptions({
reports: optionLists.reports,
personalDetails: optionLists.personalDetails,
});
}, [personalDetails, reports]);

const initializeOptions = useCallback(() => {
if (areOptionsInitialized.current) {
return;
}

loadOptions();
areOptionsInitialized.current = true;
}, [loadOptions]);

return (
<OptionsListContext.Provider value={useMemo(() => ({options, initializeOptions, areOptionsInitialized: areOptionsInitialized.current}), [options, initializeOptions])}>
{children}
</OptionsListContext.Provider>
);
}

const useOptionsListContext = () => useContext(OptionsListContext);

// Hook to use the OptionsListContext with an initializer to load the options
const useOptionsList = (options?: {shouldInitialize: boolean}) => {
const {shouldInitialize = true} = options ?? {};
const {initializeOptions, options: optionsList, areOptionsInitialized} = useOptionsListContext();

useEffect(() => {
if (!shouldInitialize || areOptionsInitialized) {
return;
}

initializeOptions();
}, [shouldInitialize, initializeOptions, areOptionsInitialized]);

return {
initializeOptions,
options: optionsList,
areOptionsInitialized,
};
};

export default withOnyx<OptionsListProviderProps, OptionsListProviderOnyxProps>({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
})(OptionsListContextProvider);

export {useOptionsListContext, useOptionsList, OptionsListContext};
2 changes: 1 addition & 1 deletion src/components/TagPicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRe
}, [selectedOptions, policyTagList, shouldShowDisabledAndSelectedOption]);

const sections = useMemo(
() => OptionsListUtils.getFilteredOptions({}, {}, [], searchValue, selectedOptions, [], false, false, false, {}, [], true, enabledTags, policyRecentlyUsedTagsList, false).tagOptions,
() => OptionsListUtils.getFilteredOptions([], [], [], searchValue, selectedOptions, [], false, false, false, {}, [], true, enabledTags, policyRecentlyUsedTagsList, false).tagOptions,
[searchValue, enabledTags, selectedOptions, policyRecentlyUsedTagsList],
);

Expand Down
Loading

0 comments on commit 398636a

Please sign in to comment.