Skip to content

Commit 0baf8e3

Browse files
authored
Merge pull request Expensify#66755 from shubham1206agra/refactor-onyx-1
Refactored the localeCompare to use hook useLocalize
2 parents 41e9022 + bed49bc commit 0baf8e3

30 files changed

Lines changed: 157 additions & 142 deletions

src/components/LocaleContextProvider.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ type LocaleContextProps = {
4646
/** Gets the standard digit corresponding to a locale digit */
4747
fromLocaleDigit: (digit: string) => string;
4848

49+
/** This is a wrapper around the localeCompare function that uses the preferred locale from the user's settings. */
50+
localeCompare: (a: string, b: string) => number;
51+
4952
/** The user's preferred locale e.g. 'en', 'es' */
5053
preferredLocale: Locale | undefined;
5154
};
@@ -60,9 +63,12 @@ const LocaleContext = createContext<LocaleContextProps>({
6063
toLocaleDigit: () => '',
6164
toLocaleOrdinal: () => '',
6265
fromLocaleDigit: () => '',
66+
localeCompare: () => 0,
6367
preferredLocale: undefined,
6468
});
6569

70+
const COLLATOR_OPTIONS: Intl.CollatorOptions = {usage: 'sort', sensitivity: 'variant', numeric: true, caseFirst: 'upper'};
71+
6672
function LocaleContextProvider({children}: LocaleContextProviderProps) {
6773
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
6874
const [areTranslationsLoading = true] = useOnyx(ONYXKEYS.ARE_TRANSLATIONS_LOADING, {initWithStoredValues: false, canBeMissing: true});
@@ -83,6 +89,8 @@ function LocaleContextProvider({children}: LocaleContextProviderProps) {
8389

8490
const selectedTimezone = useMemo(() => currentUserPersonalDetails?.timezone?.selected, [currentUserPersonalDetails]);
8591

92+
const collator = useMemo(() => new Intl.Collator(currentLocale, COLLATOR_OPTIONS), [currentLocale]);
93+
8694
const translate = useMemo<LocaleContextProps['translate']>(
8795
() =>
8896
(path, ...parameters) =>
@@ -119,6 +127,8 @@ function LocaleContextProvider({children}: LocaleContextProviderProps) {
119127

120128
const fromLocaleDigit = useMemo<LocaleContextProps['fromLocaleDigit']>(() => (localeDigit) => fromLocaleDigitLocaleDigitUtils(currentLocale, localeDigit), [currentLocale]);
121129

130+
const localeCompare = useMemo<LocaleContextProps['localeCompare']>(() => (a, b) => collator.compare(a, b), [collator]);
131+
122132
const contextValue = useMemo<LocaleContextProps>(
123133
() => ({
124134
translate,
@@ -130,9 +140,22 @@ function LocaleContextProvider({children}: LocaleContextProviderProps) {
130140
toLocaleDigit,
131141
toLocaleOrdinal,
132142
fromLocaleDigit,
143+
localeCompare,
133144
preferredLocale: currentLocale,
134145
}),
135-
[translate, numberFormat, getLocalDateFromDatetime, datetimeToRelative, datetimeToCalendarTime, formatPhoneNumber, toLocaleDigit, toLocaleOrdinal, fromLocaleDigit, currentLocale],
146+
[
147+
translate,
148+
numberFormat,
149+
getLocalDateFromDatetime,
150+
datetimeToRelative,
151+
datetimeToCalendarTime,
152+
formatPhoneNumber,
153+
toLocaleDigit,
154+
toLocaleOrdinal,
155+
fromLocaleDigit,
156+
localeCompare,
157+
currentLocale,
158+
],
136159
);
137160

138161
return <LocaleContext.Provider value={contextValue}>{children}</LocaleContext.Provider>;

src/components/Search/SearchAutocompleteList.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ function SearchAutocompleteList(
166166
ref: ForwardedRef<SelectionListHandle>,
167167
) {
168168
const styles = useThemeStyles();
169-
const {translate} = useLocalize();
169+
const {translate, localeCompare} = useLocalize();
170170
const {shouldUseNarrowLayout} = useResponsiveLayout();
171171

172172
const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true});
@@ -503,8 +503,8 @@ function SearchAutocompleteList(
503503
]);
504504

505505
const sortedRecentSearches = useMemo(() => {
506-
return Object.values(recentSearches ?? {}).sort((a, b) => b.timestamp.localeCompare(a.timestamp));
507-
}, [recentSearches]);
506+
return Object.values(recentSearches ?? {}).sort((a, b) => localeCompare(b.timestamp, a.timestamp));
507+
}, [recentSearches, localeCompare]);
508508

509509
const recentSearchesData = sortedRecentSearches?.slice(0, 5).map(({query, timestamp}) => {
510510
const searchQueryJSON = buildSearchQueryJSON(query);

src/components/TagPicker/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ function TagPicker({
5858
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true});
5959
const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, {canBeMissing: true});
6060
const styles = useThemeStyles();
61-
const {translate} = useLocalize();
61+
const {translate, localeCompare} = useLocalize();
6262
const [searchValue, setSearchValue] = useState('');
6363

6464
const policyRecentlyUsedTagsList = useMemo(() => policyRecentlyUsedTags?.[tagListName] ?? [], [policyRecentlyUsedTags, tagListName]);
@@ -117,10 +117,10 @@ function TagPicker({
117117
return shouldOrderListByTagName
118118
? tagSections.map((option) => ({
119119
...option,
120-
data: option.data.sort((a, b) => a.text?.localeCompare(b.text ?? '') ?? 0),
120+
data: option.data.sort((a, b) => localeCompare(a.text ?? '', b.text ?? '')),
121121
}))
122122
: tagSections;
123-
}, [searchValue, selectedOptions, enabledTags, policyRecentlyUsedTagsList, shouldOrderListByTagName]);
123+
}, [searchValue, selectedOptions, enabledTags, policyRecentlyUsedTagsList, shouldOrderListByTagName, localeCompare]);
124124

125125
const headerMessage = getHeaderMessageForNonUserList((sections?.at(0)?.data?.length ?? 0) > 0, searchValue);
126126

src/hooks/useCardFeedsForDisplay.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {useMemo} from 'react';
22
import {getCardFeedsForDisplayPerPolicy} from '@libs/CardFeedUtils';
33
import {isPaidGroupPolicy} from '@libs/PolicyUtils';
44
import ONYXKEYS from '@src/ONYXKEYS';
5+
import useLocalize from './useLocalize';
56
import useOnyx from './useOnyx';
67

78
const useCardFeedsForDisplay = () => {
9+
const {localeCompare} = useLocalize();
810
const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true});
911
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true});
1012
const [eligiblePoliciesIDs] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
@@ -30,18 +32,18 @@ const useCardFeedsForDisplay = () => {
3032
if (activePolicyID && eligiblePoliciesIDs.has(activePolicyID)) {
3133
const policyCardFeeds = cardFeedsByPolicy[activePolicyID];
3234
if (policyCardFeeds?.length) {
33-
return policyCardFeeds.sort((a, b) => a.name.localeCompare(b.name)).at(0);
35+
return policyCardFeeds.sort((a, b) => localeCompare(a.name, b.name)).at(0);
3436
}
3537
}
3638

3739
// If the active policy doesn't have card feeds, use the first eligible policy that does
3840
for (const eligiblePolicyID of eligiblePoliciesIDs) {
3941
const policyCardFeeds = cardFeedsByPolicy[eligiblePolicyID];
4042
if (policyCardFeeds?.length) {
41-
return policyCardFeeds.sort((a, b) => a.name.localeCompare(b.name)).at(0);
43+
return policyCardFeeds.sort((a, b) => localeCompare(a.name, b.name)).at(0);
4244
}
4345
}
44-
}, [eligiblePoliciesIDs, activePolicyID, cardFeedsByPolicy]);
46+
}, [eligiblePoliciesIDs, activePolicyID, cardFeedsByPolicy, localeCompare]);
4547

4648
return {defaultCardFeed, cardFeedsByPolicy};
4749
};

src/libs/KeyboardShortcut/index.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {Str} from 'expensify-common';
22
import * as KeyCommand from 'react-native-key-command';
33
import getOperatingSystem from '@libs/getOperatingSystem';
4-
import localeCompare from '@libs/LocaleCompare';
54
import CONST from '@src/CONST';
65
import bindHandlerToKeydownEvent from './bindHandlerToKeydownEvent';
76

@@ -32,10 +31,6 @@ type Shortcut = {
3231
// Documentation information for keyboard shortcuts that are displayed in the keyboard shortcuts informational modal
3332
const documentedShortcuts: Record<string, Shortcut> = {};
3433

35-
function getDocumentedShortcuts(): Shortcut[] {
36-
return Object.values(documentedShortcuts).sort((a, b) => localeCompare(a.displayName, b.displayName));
37-
}
38-
3934
const keyInputEnter = KeyCommand?.constants?.keyInputEnter?.toString() ?? 'keyInputEnter';
4035
const keyInputEscape = KeyCommand?.constants?.keyInputEscape?.toString() ?? 'keyInputEscape';
4136
const keyInputUpArrow = KeyCommand?.constants?.keyInputUpArrow?.toString() ?? 'keyInputUpArrow';
@@ -192,7 +187,6 @@ function subscribe(
192187
const KeyboardShortcut = {
193188
subscribe,
194189
getDisplayName,
195-
getDocumentedShortcuts,
196190
getPlatformEquivalentForKeys,
197191
};
198192

src/libs/SuggestionUtils.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import CONST from '@src/CONST';
2+
import type {PersonalDetails} from '@src/types/onyx';
3+
import localeCompare from './LocaleCompare';
4+
import {getDisplayNameForParticipant} from './ReportUtils';
25

36
/**
47
* Trims first character of the string if it is a space
@@ -20,4 +23,28 @@ function hasEnoughSpaceForLargeSuggestionMenu(listHeight: number, composerHeight
2023
return availableHeight > menuHeight;
2124
}
2225

23-
export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu};
26+
function getDisplayName(details: PersonalDetails) {
27+
const displayNameFromAccountID = getDisplayNameForParticipant({accountID: details.accountID});
28+
if (!displayNameFromAccountID) {
29+
return details.login?.length ? details.login : '';
30+
}
31+
return displayNameFromAccountID;
32+
}
33+
34+
/**
35+
* Comparison function to sort users. It compares weights, display names, and accountIDs in that order
36+
*/
37+
function compareUserInList(first: PersonalDetails & {weight: number}, second: PersonalDetails & {weight: number}) {
38+
if (first.weight !== second.weight) {
39+
return first.weight - second.weight;
40+
}
41+
42+
const displayNameLoginOrder = localeCompare(getDisplayName(first), getDisplayName(second));
43+
if (displayNameLoginOrder !== 0) {
44+
return displayNameLoginOrder;
45+
}
46+
47+
return first.accountID - second.accountID;
48+
}
49+
50+
export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, compareUserInList};

src/libs/TaxOptionsListUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,5 @@ function getTaxRatesSection({
145145
return policyRatesSections;
146146
}
147147

148-
export {getTaxRatesSection, sortTaxRates, getTaxRatesOptions};
148+
export {getTaxRatesSection, getTaxRatesOptions};
149149
export type {TaxRatesOption, Tax};

src/pages/Debug/DebugDetails.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ type DebugDetailsProps = {
5353
};
5454

5555
function DebugDetails({formType, data, policyHasEnabledTags, policyID, children, onSave, onDelete, validate}: DebugDetailsProps) {
56-
const {translate} = useLocalize();
56+
const {translate, localeCompare} = useLocalize();
5757
const styles = useThemeStyles();
5858
const [formDraftData] = useOnyx(ONYXKEYS.FORMS.DEBUG_DETAILS_FORM_DRAFT, {canBeMissing: true});
5959
const booleanFields = useMemo(
6060
() =>
6161
Object.entries(data ?? {})
6262
.filter(([, value]) => typeof value === 'boolean')
63-
.sort((a, b) => a[0].localeCompare(b[0])) as Array<[string, boolean]>,
64-
[data],
63+
.sort((a, b) => localeCompare(a[0], b[0])) as Array<[string, boolean]>,
64+
[data, localeCompare],
6565
);
6666
const constantFields = useMemo(
6767
() =>
@@ -73,15 +73,15 @@ function DebugDetails({formType, data, policyHasEnabledTags, policyID, children,
7373
}
7474
return DETAILS_CONSTANT_FIELDS[formType].some(({fieldName}) => fieldName === entry[0]);
7575
})
76-
.sort((a, b) => a[0].localeCompare(b[0])),
77-
[data, formType, policyHasEnabledTags],
76+
.sort((a, b) => localeCompare(a[0], b[0])),
77+
[data, formType, policyHasEnabledTags, localeCompare],
7878
);
7979
const numberFields = useMemo(
8080
() =>
8181
Object.entries(data ?? {})
8282
.filter((entry): entry is [string, number] => typeof entry[1] === 'number')
83-
.sort((a, b) => a[0].localeCompare(b[0])),
84-
[data],
83+
.sort((a, b) => localeCompare(a[0], b[0])),
84+
[data, localeCompare],
8585
);
8686
const textFields = useMemo(
8787
() =>
@@ -93,8 +93,8 @@ function DebugDetails({formType, data, policyHasEnabledTags, policyID, children,
9393
!DETAILS_DATETIME_FIELDS.includes(entry[0]),
9494
)
9595
.map(([key, value]) => [key, DebugUtils.onyxDataToString(value)])
96-
.sort((a, b) => (a.at(0) ?? '').localeCompare(b.at(0) ?? '')),
97-
[data, formType],
96+
.sort((a, b) => localeCompare(a.at(0) ?? '', b.at(0) ?? '')),
97+
[data, formType, localeCompare],
9898
);
9999
const dateTimeFields = useMemo(() => Object.entries(data ?? {}).filter((entry): entry is [string, string] => DETAILS_DATETIME_FIELDS.includes(entry[0])), [data]);
100100

src/pages/EditReportFieldDropdown.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ import useDebouncedState from '@hooks/useDebouncedState';
88
import useLocalize from '@hooks/useLocalize';
99
import useOnyx from '@hooks/useOnyx';
1010
import useTheme from '@hooks/useTheme';
11-
import localeCompare from '@libs/LocaleCompare';
12-
import * as OptionsListUtils from '@libs/OptionsListUtils';
13-
import * as ReportFieldOptionsListUtils from '@libs/ReportFieldOptionsListUtils';
11+
import {getHeaderMessageForNonUserList} from '@libs/OptionsListUtils';
12+
import {getReportFieldOptionsSection} from '@libs/ReportFieldOptionsListUtils';
1413
import ONYXKEYS from '@src/ONYXKEYS';
1514

1615
type EditReportFieldDropdownPageProps = {
@@ -28,11 +27,11 @@ type EditReportFieldDropdownPageProps = {
2827
};
2928

3029
function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptions}: EditReportFieldDropdownPageProps) {
31-
const [recentlyUsedReportFields] = useOnyx(ONYXKEYS.RECENTLY_USED_REPORT_FIELDS);
30+
const [recentlyUsedReportFields] = useOnyx(ONYXKEYS.RECENTLY_USED_REPORT_FIELDS, {canBeMissing: true});
3231
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
3332
const theme = useTheme();
34-
const {translate} = useLocalize();
35-
const recentlyUsedOptions = useMemo(() => recentlyUsedReportFields?.[fieldKey]?.sort(localeCompare) ?? [], [recentlyUsedReportFields, fieldKey]);
33+
const {translate, localeCompare} = useLocalize();
34+
const recentlyUsedOptions = useMemo(() => recentlyUsedReportFields?.[fieldKey]?.sort(localeCompare) ?? [], [recentlyUsedReportFields, fieldKey, localeCompare]);
3635

3736
const itemRightSideComponent = useCallback(
3837
(item: ListItem) => {
@@ -53,7 +52,7 @@ function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptio
5352
const [sections, headerMessage] = useMemo(() => {
5453
const validFieldOptions = fieldOptions?.filter((option) => !!option)?.sort(localeCompare);
5554

56-
const policyReportFieldOptions = ReportFieldOptionsListUtils.getReportFieldOptionsSection({
55+
const policyReportFieldOptions = getReportFieldOptionsSection({
5756
searchValue: debouncedSearchValue,
5857
selectedOptions: [
5958
{
@@ -67,10 +66,10 @@ function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptio
6766
});
6867

6968
const policyReportFieldData = policyReportFieldOptions.at(0)?.data ?? [];
70-
const header = OptionsListUtils.getHeaderMessageForNonUserList(policyReportFieldData.length > 0, debouncedSearchValue);
69+
const header = getHeaderMessageForNonUserList(policyReportFieldData.length > 0, debouncedSearchValue);
7170

7271
return [policyReportFieldOptions, header];
73-
}, [recentlyUsedOptions, debouncedSearchValue, fieldValue, fieldOptions]);
72+
}, [fieldOptions, localeCompare, debouncedSearchValue, fieldValue, recentlyUsedOptions]);
7473

7574
const selectedOptionKey = useMemo(() => (sections.at(0)?.data ?? []).filter((option) => option.searchText === fieldValue)?.at(0)?.keyForList, [sections, fieldValue]);
7675
return (

src/pages/NewChatConfirmPage.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ function navigateToEditChatName() {
3636
function NewChatConfirmPage() {
3737
const optimisticReportID = useRef<string>(generateReportID());
3838
const [avatarFile, setAvatarFile] = useState<File | CustomRNImageManipulatorResult | undefined>();
39-
const {translate} = useLocalize();
39+
const {translate, localeCompare} = useLocalize();
4040
const styles = useThemeStyles();
4141
const personalData = useCurrentUserPersonalDetails();
42-
const [newGroupDraft, newGroupDraftMetaData] = useOnyx(ONYXKEYS.NEW_GROUP_CHAT_DRAFT);
43-
const [allPersonalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
42+
const [newGroupDraft, newGroupDraftMetaData] = useOnyx(ONYXKEYS.NEW_GROUP_CHAT_DRAFT, {canBeMissing: true});
43+
const [allPersonalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false});
4444

4545
const selectedOptions = useMemo((): Participant[] => {
4646
if (!newGroupDraft?.participants) {
@@ -72,8 +72,8 @@ function NewChatConfirmPage() {
7272
};
7373
return section;
7474
})
75-
.sort((a, b) => a.text?.toLowerCase().localeCompare(b.text?.toLowerCase() ?? '') ?? -1),
76-
[selectedOptions, personalData.accountID, translate],
75+
.sort((a, b) => localeCompare(a.text?.toLowerCase() ?? '', b.text?.toLowerCase() ?? '')),
76+
[selectedOptions, personalData.accountID, translate, localeCompare],
7777
);
7878

7979
/**

0 commit comments

Comments
 (0)