diff --git a/package-lock.json b/package-lock.json index a46198daa88a..70aa06990e3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,7 +100,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.32", + "react-native-onyx": "2.0.41", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -238,7 +238,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "type-fest": "^4.10.2", - "typescript": "^5.3.2", + "typescript": "^5.4.5", "wait-port": "^0.2.9", "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.5.0", @@ -31431,9 +31431,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.32", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.32.tgz", - "integrity": "sha512-tB9wqMJGTLOYfrfplRP+9aq5JdD8w/hV/OZsMAVH+ewbE1zLY8OymUsAsIFdF1v+cB8HhehP569JVLZmhm6bsg==", + "version": "2.0.41", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.41.tgz", + "integrity": "sha512-33r0sVBq7MV/GZwRneRt81uxgW8x3YG75VNJvThycB/dkCnGCfbxoVkZADVH3ET3jzfFXy9wnS06sZnZp78zMQ==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -36029,9 +36029,10 @@ } }, "node_modules/typescript": { - "version": "5.3.3", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "devOptional": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 1a449f42bece..126c2d80e79a 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.32", + "react-native-onyx": "2.0.41", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -290,7 +290,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "type-fest": "^4.10.2", - "typescript": "^5.3.2", + "typescript": "^5.4.5", "wait-port": "^0.2.9", "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.5.0", diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index 9ad4643e834a..296ecce7d092 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -4,7 +4,6 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import type {MaybePhraseKey} from '@libs/Localize'; -import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; @@ -149,8 +148,6 @@ function AddressForm({ label={translate('common.addressLine', {lineNumber: 1})} onValueChange={(data: unknown, key: unknown) => { onAddressChanged(data, key); - // This enforces the country selector to use the country from address instead of the country from URL - Navigation.setParams({country: undefined}); }} defaultValue={street1} renamedInputKeys={{ diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index fb6a8e911e87..de98ba79d23c 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -607,7 +607,6 @@ export default withOnyx({ const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage.IOUTransactionID ?? '0' : '0'; return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, - initWithStoredValues: false, }, })(memo(AttachmentModal)); diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 8942bf97a7dd..156c27abbc9d 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -12,7 +12,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, Policy, Report, ReportActions} from '@src/types/onyx'; import DisplayNames from './DisplayNames'; import MultipleAvatars from './MultipleAvatars'; import ParentNavigationSubtitle from './ParentNavigationSubtitle'; @@ -25,7 +25,7 @@ type AvatarWithDisplayNamePropsWithOnyx = { parentReportActions: OnyxEntry; /** Personal details of all users */ - personalDetails: OnyxCollection; + personalDetails: OnyxEntry; }; type AvatarWithDisplayNameProps = AvatarWithDisplayNamePropsWithOnyx & { diff --git a/src/components/KeyboardAvoidingView/index.ios.tsx b/src/components/KeyboardAvoidingView/index.ios.tsx index a7cd767377ef..171210eab7ac 100644 --- a/src/components/KeyboardAvoidingView/index.ios.tsx +++ b/src/components/KeyboardAvoidingView/index.ios.tsx @@ -3,7 +3,7 @@ */ import React from 'react'; import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native'; -import type KeyboardAvoidingViewProps from './types'; +import type {KeyboardAvoidingViewProps} from './types'; function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) { // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/KeyboardAvoidingView/index.tsx b/src/components/KeyboardAvoidingView/index.tsx index 09ec21e5b219..c0882ae1e9cc 100644 --- a/src/components/KeyboardAvoidingView/index.tsx +++ b/src/components/KeyboardAvoidingView/index.tsx @@ -3,7 +3,7 @@ */ import React from 'react'; import {View} from 'react-native'; -import type KeyboardAvoidingViewProps from './types'; +import type {KeyboardAvoidingViewProps} from './types'; function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) { const {behavior, contentContainerStyle, enabled, keyboardVerticalOffset, ...rest} = props; diff --git a/src/components/KeyboardAvoidingView/types.ts b/src/components/KeyboardAvoidingView/types.ts index 48d354e8b53f..2c1ef64ced8f 100644 --- a/src/components/KeyboardAvoidingView/types.ts +++ b/src/components/KeyboardAvoidingView/types.ts @@ -1,3 +1,4 @@ -import {KeyboardAvoidingViewProps} from 'react-native'; +import type {KeyboardAvoidingViewProps} from 'react-native'; -export default KeyboardAvoidingViewProps; +// eslint-disable-next-line import/prefer-default-export +export type {KeyboardAvoidingViewProps}; diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 77b296740f2c..3e0ca80b5e41 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -26,6 +26,7 @@ import Log from '@libs/Log'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import arraysEqual from '@src/utils/arraysEqual'; import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, ListItem, SectionListDataType, SectionWithIndexOffset, SelectionListHandle} from './types'; function BaseSelectionList( @@ -228,11 +229,21 @@ function BaseSelectionList( [flattenedSections.allOptions], ); + const [disabledIndexes, setDisabledIndexes] = useState(flattenedSections.disabledOptionsIndexes); + useEffect(() => { + if (arraysEqual(disabledIndexes, flattenedSections.disabledOptionsIndexes)) { + return; + } + + setDisabledIndexes(flattenedSections.disabledOptionsIndexes); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [flattenedSections.disabledOptionsIndexes]); + // If `initiallyFocusedOptionKey` is not passed, we fall back to `-1`, to avoid showing the highlight on the first member const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({ initialFocusedIndex: flattenedSections.allOptions.findIndex((option) => option.keyForList === initiallyFocusedOptionKey), maxIndex: Math.min(flattenedSections.allOptions.length - 1, CONST.MAX_SELECTION_LIST_PAGE_LENGTH * currentPage - 1), - disabledIndexes: flattenedSections.disabledOptionsIndexes, + disabledIndexes, isActive: true, onFocusedIndexChange: (index: number) => { scrollToIndex(index, true); diff --git a/src/languages/types.ts b/src/languages/types.ts index e2e7e26e696b..ef984b9e640a 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -1,3 +1,4 @@ +import type {OnyxEntry} from 'react-native-onyx'; import type {ReportAction} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import type en from './en'; @@ -40,15 +41,15 @@ type LocalTimeParams = { }; type EditActionParams = { - action: ReportAction | null; + action: OnyxEntry; }; type DeleteActionParams = { - action: ReportAction | null; + action: OnyxEntry; }; type DeleteConfirmationParams = { - action: ReportAction | null; + action: OnyxEntry; }; type BeginningOfChatHistoryDomainRoomPartOneParams = { @@ -299,11 +300,10 @@ type DistanceRateOperationsParams = {count: number}; type ReimbursementRateParams = {unit: Unit}; export type { - AdminCanceledRequestParams, - ApprovedAmountParams, AddressLineParams, + AdminCanceledRequestParams, AlreadySignedInParams, - UserSplitParams, + ApprovedAmountParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, @@ -324,8 +324,10 @@ export type { FormattedMaxLengthParams, GoBackMessageParams, GoToRoomParams, + HeldRequestParams, InstantSummaryParams, LocalTimeParams, + LogSizeParams, LoggedInAsParams, ManagerApprovedAmountParams, ManagerApprovedParams, @@ -339,11 +341,13 @@ export type { PaidElsewhereWithAmountParams, PaidWithExpensifyWithAmountParams, ParentNavigationSummaryParams, + PaySomeoneParams, PayerOwesAmountParams, PayerOwesParams, PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, + ReimbursementRateParams, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, @@ -375,7 +379,9 @@ export type { UntilTimeParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, + UsePlusButtonParams, UserIsAlreadyMemberParams, + UserSplitParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, @@ -392,14 +398,9 @@ export type { ViolationsTaxOutOfPolicyParams, WaitingOnBankAccountParams, WalletProgramParams, - UsePlusButtonParams, WeSentYouMagicSignInLinkParams, WelcomeEnterMagicCodeParams, WelcomeNoteParams, WelcomeToRoomParams, ZipCodeExampleFormatParams, - LogSizeParams, - HeldRequestParams, - PaySomeoneParams, - ReimbursementRateParams, }; diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 3b189dbb084f..c8b384a5a97c 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -431,7 +431,7 @@ function suggestEmojis(text: string, lang: Locale, limit: number = CONST.AUTO_CO /** * Retrieve preferredSkinTone as Number to prevent legacy 'default' String value */ -const getPreferredSkinToneIndex = (value: string | number | null): number => { +const getPreferredSkinToneIndex = (value: OnyxEntry): number => { if (value !== null && Number.isInteger(Number(value))) { return Number(value); } diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 3487f05b9c05..ecaf563d59d1 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -1,4 +1,5 @@ import mapValues from 'lodash/mapValues'; +import type {OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; @@ -61,7 +62,7 @@ type OnyxDataWithErrors = { errors?: Errors | null; }; -function getLatestErrorMessage(onyxData: TOnyxData | null): Localize.MaybePhraseKey { +function getLatestErrorMessage(onyxData: OnyxEntry): Localize.MaybePhraseKey { const errors = onyxData?.errors ?? {}; if (Object.keys(errors).length === 0) { @@ -72,8 +73,8 @@ function getLatestErrorMessage(onyxData: T return getErrorMessageWithTranslationData(errors[key]); } -function getLatestErrorMessageField(onyxData: TOnyxData): Errors { - const errors = onyxData.errors ?? {}; +function getLatestErrorMessageField(onyxData: OnyxEntry): Errors { + const errors = onyxData?.errors ?? {}; if (Object.keys(errors).length === 0) { return {}; @@ -88,8 +89,8 @@ type OnyxDataWithErrorFields = { errorFields?: ErrorFields; }; -function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Errors { - const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; +function getLatestErrorField(onyxData: OnyxEntry, fieldName: string): Errors { + const errorsForField = onyxData?.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { return {}; @@ -99,8 +100,8 @@ function getLatestErrorField(onyxData return {[key]: getErrorMessageWithTranslationData(errorsForField[key])}; } -function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Errors { - const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; +function getEarliestErrorField(onyxData: OnyxEntry, fieldName: string): Errors { + const errorsForField = onyxData?.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { return {}; @@ -113,8 +114,8 @@ function getEarliestErrorField(onyxDa /** * Method used to get the latest error field for any field */ -function getLatestErrorFieldForAnyField(onyxData: TOnyxData): Errors { - const errorFields = onyxData.errorFields ?? {}; +function getLatestErrorFieldForAnyField(onyxData: OnyxEntry): Errors { + const errorFields = onyxData?.errorFields ?? {}; if (Object.keys(errorFields).length === 0) { return {}; @@ -189,9 +190,9 @@ export { getErrorMessageWithTranslationData, getErrorsWithTranslationData, getLatestErrorField, + getLatestErrorFieldForAnyField, getLatestErrorMessage, getLatestErrorMessageField, - getLatestErrorFieldForAnyField, getMicroSecondOnyxError, getMicroSecondOnyxErrorObject, isReceiptError, diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 6a26b1a6cfc2..0343fa0b2c56 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -30,7 +30,7 @@ Onyx.connect({ */ function getActivePolicies(policies: OnyxCollection): Policy[] { return Object.values(policies ?? {}).filter( - (policy): policy is Policy => policy !== null && policy && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !!policy.name && !!policy.id, + (policy): policy is Policy => !!policy && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !!policy.name && !!policy.id, ); } diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index 48c5e5c1409f..e71ab96066e8 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -166,7 +166,7 @@ function bindEventToChannel(channel: Channel let data; try { - data = isObject(eventData) ? eventData : JSON.parse(eventData); + data = isObject(eventData) ? eventData : JSON.parse(eventData as string); } catch (err) { Log.alert('[Pusher] Unable to parse single JSON event data from Pusher', {error: err, eventData}); return; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 906a2963baa8..9f6ad81e46b1 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -644,7 +644,7 @@ function getLastVisibleMessage(reportID: string, actionsToMerge: OnyxCollection< /** * A helper method to filter out report actions keyed by sequenceNumbers. */ -function filterOutDeprecatedReportActions(reportActions: ReportActions | null): ReportAction[] { +function filterOutDeprecatedReportActions(reportActions: OnyxEntry): ReportAction[] { return Object.entries(reportActions ?? {}) .filter(([key, reportAction]) => !isReportActionDeprecated(reportAction, key)) .map((entry) => entry[1]); @@ -656,7 +656,7 @@ function filterOutDeprecatedReportActions(reportActions: ReportActions | null): * to ensure they will always be displayed in the same order (in case multiple actions have the same timestamp). * This is all handled with getSortedReportActions() which is used by several other methods to keep the code DRY. */ -function getSortedReportActionsForDisplay(reportActions: ReportActions | null | ReportAction[], shouldIncludeInvisibleActions = false): ReportAction[] { +function getSortedReportActionsForDisplay(reportActions: OnyxEntry | ReportAction[], shouldIncludeInvisibleActions = false): ReportAction[] { let filteredReportActions: ReportAction[] = []; if (!reportActions) { return []; @@ -680,7 +680,7 @@ function getSortedReportActionsForDisplay(reportActions: ReportActions | null | * Additionally, archived #admins and #announce do not have the closed report action so we will return null if none is found. * */ -function getLastClosedReportAction(reportActions: ReportActions | null): OnyxEntry { +function getLastClosedReportAction(reportActions: OnyxEntry): OnyxEntry { // If closed report action is not present, return early if (!Object.values(reportActions ?? {}).some((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED)) { return null; @@ -733,7 +733,7 @@ function getLinkedTransactionID(reportActionOrID: string | OnyxEntry { +function getReportAction(reportID: string, reportActionID: string): ReportAction | null { return allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]?.[reportActionID] ?? null; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b8e4c448fdc2..c5d31356af1a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -506,7 +506,7 @@ Onyx.connect({ }, }); -let allPersonalDetails: OnyxCollection; +let allPersonalDetails: OnyxEntry; let allPersonalDetailLogins: string[]; let currentUserPersonalDetails: OnyxEntry; Onyx.connect({ @@ -1643,7 +1643,7 @@ function getReportRecipientAccountIDs(report: OnyxEntry, currentLoginAcc /** * Whether the time row should be shown for a report. */ -function canShowReportRecipientLocalTime(personalDetails: OnyxCollection, report: OnyxEntry, accountID: number): boolean { +function canShowReportRecipientLocalTime(personalDetails: OnyxEntry, report: OnyxEntry, accountID: number): boolean { const reportRecipientAccountIDs = getReportRecipientAccountIDs(report, accountID); const hasMultipleParticipants = reportRecipientAccountIDs.length > 1; const reportRecipient = personalDetails?.[reportRecipientAccountIDs[0]]; @@ -1727,7 +1727,7 @@ function getDefaultGroupAvatar(reportID?: string): IconAsset { * Returns the appropriate icons for the given chat report using the stored personalDetails. * The Avatar sources can be URLs or Icon components according to the chat type. */ -function getIconsForParticipants(participants: number[], personalDetails: OnyxCollection): Icon[] { +function getIconsForParticipants(participants: number[], personalDetails: OnyxEntry): Icon[] { const participantDetails: ParticipantDetails[] = []; const participantsList = participants || []; @@ -1912,7 +1912,7 @@ function getParticipants(reportID: string) { */ function getIcons( report: OnyxEntry, - personalDetails: OnyxCollection, + personalDetails: OnyxEntry, defaultIcon: UserUtils.AvatarSource | null = null, defaultName = '', defaultAccountID = -1, diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 79e73f1585d2..46d37c54f087 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -366,7 +366,7 @@ function getMerchant(transaction: OnyxEntry): string { return transaction?.modifiedMerchant ? transaction.modifiedMerchant : transaction?.merchant ?? ''; } -function getDistance(transaction: Transaction | null): number { +function getDistance(transaction: OnyxEntry): number { return transaction?.comment?.customUnit?.quantity ?? 0; } diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index cddb2c371a60..fdb21eedbf84 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -1,4 +1,3 @@ -import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import type {OnfidoDataWithApplicantID} from '@components/Onfido/types'; @@ -70,7 +69,7 @@ function openPlaidView() { clearPlaid().then(() => ReimbursementAccount.setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID)); } -function setPlaidEvent(eventName: OnyxEntry) { +function setPlaidEvent(eventName: string | null) { Onyx.set(ONYXKEYS.PLAID_CURRENT_EVENT, eventName); } diff --git a/src/libs/actions/FormActions.ts b/src/libs/actions/FormActions.ts index 1fcf9bed6a55..5fe1705d8db3 100644 --- a/src/libs/actions/FormActions.ts +++ b/src/libs/actions/FormActions.ts @@ -24,7 +24,7 @@ function clearErrorFields(formID: OnyxFormKey) { } function setDraftValues(formID: OnyxFormKey, draftValues: NullishDeep>) { - Onyx.merge(`${formID}Draft`, draftValues); + Onyx.merge(`${formID}Draft`, draftValues ?? null); } function clearDraftValues(formID: OnyxFormKey) { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 2464dbff7dbd..0bc72a387227 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1356,7 +1356,7 @@ function buildOnyxDataForTrackExpense( failureData.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, - value: quickAction, + value: quickAction ?? null, }); if (iouReport) { @@ -1590,7 +1590,7 @@ function getDeleteTrackExpenseInformation( failureData.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - value: transaction, + value: transaction ?? null, }); } @@ -1598,7 +1598,7 @@ function getDeleteTrackExpenseInformation( failureData.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - value: transactionViolations, + value: transactionViolations ?? null, }); } @@ -1660,7 +1660,7 @@ function getSendInvoiceInformation( let chatReport = !isEmptyObject(invoiceChatReport) && invoiceChatReport?.reportID ? invoiceChatReport : null; if (!chatReport) { - chatReport = ReportUtils.getInvoiceChatByParticipants(senderWorkspaceID, receiverAccountID); + chatReport = ReportUtils.getInvoiceChatByParticipants(senderWorkspaceID, receiverAccountID) ?? null; } if (!chatReport) { @@ -1814,7 +1814,7 @@ function getMoneyRequestInformation( } if (!chatReport) { - chatReport = ReportUtils.getChatByParticipants([payerAccountID, payeeAccountID]); + chatReport = ReportUtils.getChatByParticipants([payerAccountID, payeeAccountID]) ?? null; } // If we still don't have a report, it likely doens't exist and we need to build an optimistic one @@ -5329,7 +5329,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - value: transaction, + value: transaction ?? null, }, ]; @@ -5337,7 +5337,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor failureData.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, - value: transactionViolations, + value: transactionViolations ?? null, }); } @@ -6434,7 +6434,7 @@ function detachReceipt(transactionID: string) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: { - ...transaction, + ...(transaction ?? null), errors: ErrorUtils.getMicroSecondOnyxError('iou.error.receiptDeleteFailureError'), }, }, diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts index 04656f1adfec..fc651b0599b5 100644 --- a/src/libs/actions/OnyxUpdates.ts +++ b/src/libs/actions/OnyxUpdates.ts @@ -1,4 +1,4 @@ -import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; +import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {Merge} from 'type-fest'; import Log from '@libs/Log'; @@ -13,7 +13,7 @@ import * as QueuedOnyxUpdates from './QueuedOnyxUpdates'; // This key needs to be separate from ONYXKEYS.ONYX_UPDATES_FROM_SERVER so that it can be updated without triggering the callback when the server IDs are updated. If that // callback were triggered it would lead to duplicate processing of server updates. -let lastUpdateIDAppliedToClient: OnyxEntry = 0; +let lastUpdateIDAppliedToClient: number | null = 0; Onyx.connect({ key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, callback: (val) => (lastUpdateIDAppliedToClient = val), @@ -151,4 +151,4 @@ function doesClientNeedToBeUpdated(previousUpdateID = 0, clientLastUpdateID = 0) } // eslint-disable-next-line import/prefer-default-export -export {saveUpdateInformation, doesClientNeedToBeUpdated, apply}; +export {apply, doesClientNeedToBeUpdated, saveUpdateInformation}; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 3e09524e1dd1..9206aa2fb0b3 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -258,7 +258,7 @@ Onyx.connect({ * Stores in Onyx the policy ID of the last workspace that was accessed by the user */ function updateLastAccessedWorkspace(policyID: OnyxEntry) { - Onyx.set(ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID, policyID); + Onyx.set(ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID, policyID ?? null); } /** diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 91a0ce5da930..b513162b9c75 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -85,7 +85,6 @@ import INPUT_IDS from '@src/types/form/NewRoomForm'; import type { InvitedEmailsToAccountIDs, NewGroupChatDraft, - PersonalDetails, PersonalDetailsList, PolicyReportField, QuickAction, @@ -850,8 +849,8 @@ function openReport( }); // Add optimistic personal details for new participants - const optimisticPersonalDetails: OnyxCollection = {}; - const settledPersonalDetails: OnyxCollection = {}; + const optimisticPersonalDetails: OnyxEntry = {}; + const settledPersonalDetails: OnyxEntry = {}; const redundantParticipants: Record = {}; const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(participantLoginList); participantLoginList.forEach((login, index) => { diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 217dd0b12100..4fc72bae1e7d 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -71,11 +71,11 @@ Onyx.connect({ }); /** - * + * ignore: `undefined` means we want to check both parent and children report actions ignore: `parent` or `child` means we want to ignore checking parent or child report actions because they've been previously checked */ -function clearAllRelatedReportActionErrors(reportID: string, reportAction: ReportAction | null, ignore?: IgnoreDirection, keys?: string[]) { +function clearAllRelatedReportActionErrors(reportID: string, reportAction: ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) { const errorKeys = keys ?? Object.keys(reportAction?.errors ?? {}); if (!reportAction || errorKeys.length === 0) { return; diff --git a/src/libs/migrations/RenameReceiptFilename.ts b/src/libs/migrations/RenameReceiptFilename.ts index b867024fc74e..100b279c950f 100644 --- a/src/libs/migrations/RenameReceiptFilename.ts +++ b/src/libs/migrations/RenameReceiptFilename.ts @@ -1,5 +1,5 @@ import Onyx from 'react-native-onyx'; -import type {NullishDeep, OnyxCollection} from 'react-native-onyx'; +import type {NullishDeep, OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Log from '@libs/Log'; import ONYXKEYS from '@src/ONYXKEYS'; import type Transaction from '@src/types/onyx/Transaction'; @@ -24,7 +24,7 @@ export default function () { return resolve(); } - const transactionsWithReceipt: Array = Object.values(transactions).filter((transaction) => transaction?.receiptFilename); + const transactionsWithReceipt: Array> = Object.values(transactions).filter((transaction) => transaction?.receiptFilename); if (!transactionsWithReceipt?.length) { Log.info('[Migrate Onyx] Skipped migration RenameReceiptFilename because there were no transactions with the receiptFilename property'); return resolve(); diff --git a/src/libs/migrations/TransactionBackupsToCollection.ts b/src/libs/migrations/TransactionBackupsToCollection.ts index a7167492007a..8b963d7fa0c2 100644 --- a/src/libs/migrations/TransactionBackupsToCollection.ts +++ b/src/libs/migrations/TransactionBackupsToCollection.ts @@ -1,4 +1,4 @@ -import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -30,7 +30,7 @@ export default function (): Promise { // Find all the transaction backups available Object.keys(transactions).forEach((transactionOnyxKey: string) => { - const transaction: Transaction | null = transactions[transactionOnyxKey]; + const transaction: OnyxEntry = transactions[transactionOnyxKey]; // Determine whether or not the transaction is a backup if (transactionOnyxKey.endsWith('-backup') && transaction) { diff --git a/src/pages/GroupChatNameEditPage.tsx b/src/pages/GroupChatNameEditPage.tsx index 87218fbb89cd..cb604a1e1722 100644 --- a/src/pages/GroupChatNameEditPage.tsx +++ b/src/pages/GroupChatNameEditPage.tsx @@ -1,5 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useMemo} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -24,7 +25,7 @@ import type NewGroupChatDraft from '@src/types/onyx/NewGroupChatDraft'; import type {Errors} from '@src/types/onyx/OnyxCommon'; type GroupChatNameEditPageOnyxProps = { - groupChatDraft: NewGroupChatDraft | null; + groupChatDraft: OnyxEntry; }; type GroupChatNameEditPageProps = StackScreenProps & GroupChatNameEditPageOnyxProps; diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 555a7c855d42..a70320444dc9 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -1,6 +1,7 @@ import isEmpty from 'lodash/isEmpty'; import reject from 'lodash/reject'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; @@ -31,6 +32,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Beta} from '@src/types/onyx'; import type {SelectedParticipant} from '@src/types/onyx/NewGroupChatDraft'; type NewChatPageProps = { @@ -54,7 +56,7 @@ function useOptions({isGroupChat}: NewChatPageProps) { const filteredOptions = OptionsListUtils.getFilteredOptions( listOptions.reports ?? [], listOptions.personalDetails ?? [], - betas ?? [], + (betas ?? []) as OnyxEntry, debouncedSearchTerm, selectedOptions, isGroupChat ? excludedGroupEmails : [], diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index f9560edf7486..aebdb774a8cc 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -5,7 +5,7 @@ import Str from 'expensify-common/lib/str'; import lodashDebounce from 'lodash/debounce'; import React, {useCallback, useMemo, useRef, useState} from 'react'; import {Keyboard} from 'react-native'; -import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -31,12 +31,12 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/PrivateNotesForm'; -import type {PersonalDetails, Report} from '@src/types/onyx'; +import type {PersonalDetailsList, Report} from '@src/types/onyx'; import type {Note} from '@src/types/onyx/Report'; type PrivateNotesEditPageOnyxProps = { /** All of the personal details for everyone */ - personalDetailsList: OnyxCollection; + personalDetailsList: OnyxEntry; }; type PrivateNotesEditPageProps = WithReportAndPrivateNotesOrNotFoundProps & diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.tsx b/src/pages/PrivateNotes/PrivateNotesListPage.tsx index 1893f81da2fe..cf3ce4c36b53 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesListPage.tsx @@ -1,6 +1,6 @@ import React, {useMemo} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -15,11 +15,11 @@ import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAn import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails, Report} from '@src/types/onyx'; +import type {PersonalDetailsList, Report} from '@src/types/onyx'; type PrivateNotesListPageOnyxProps = { /** All of the personal details for everyone */ - personalDetailsList: OnyxCollection; + personalDetailsList: OnyxEntry; }; type PrivateNotesListPageProps = WithReportAndPrivateNotesOrNotFoundProps & diff --git a/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx b/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx index a83e18d119c9..97a8caaef692 100644 --- a/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx +++ b/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx @@ -1,6 +1,7 @@ import Str from 'expensify-common/lib/str'; import React, {useCallback} from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; @@ -26,7 +27,7 @@ type BankAccountValidationFormProps = { requiresTwoFactorAuth: boolean; /** The policy which the user has access to and which the report is tied to */ - policy: Policy | null; + policy: OnyxEntry; }; type AmountValues = { diff --git a/src/pages/ReimbursementAccount/utils/getValuesForBeneficialOwner.ts b/src/pages/ReimbursementAccount/utils/getValuesForBeneficialOwner.ts index 3733a6727c54..b564b8f325ee 100644 --- a/src/pages/ReimbursementAccount/utils/getValuesForBeneficialOwner.ts +++ b/src/pages/ReimbursementAccount/utils/getValuesForBeneficialOwner.ts @@ -1,3 +1,4 @@ +import type {OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import type {ReimbursementAccountForm} from '@src/types/form'; @@ -12,7 +13,7 @@ type BeneficialOwnerValues = { zipCode: string; }; -function getValuesForBeneficialOwner(beneficialOwnerBeingModifiedID: string, reimbursementAccountDraft: ReimbursementAccountForm | null): BeneficialOwnerValues { +function getValuesForBeneficialOwner(beneficialOwnerBeingModifiedID: string, reimbursementAccountDraft: OnyxEntry): BeneficialOwnerValues { if (!reimbursementAccountDraft) { return { firstName: '', diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index ca66a0e97bb5..a4d7c6eb7a22 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -2,7 +2,7 @@ import {useRoute} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; @@ -53,7 +53,7 @@ type ReportDetailsPageMenuItem = { type ReportDetailsPageOnyxProps = { /** Personal details of all the users */ - personalDetails: OnyxCollection; + personalDetails: OnyxEntry; /** Session info for the currently logged in user. */ session: OnyxEntry; diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx index e7726fb89537..dd12963a7cde 100644 --- a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx +++ b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx @@ -16,8 +16,7 @@ type ProfileAvatarWithIndicatorProps = { function ProfileAvatarWithIndicator({isSelected = false}: ProfileAvatarWithIndicatorProps) { const styles = useThemeStyles(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const [isLoadingOnyxValue] = useOnyx(ONYXKEYS.IS_LOADING_APP); - const isLoading = isLoadingOnyxValue ?? true; + const [isLoading = true] = useOnyx(ONYXKEYS.IS_LOADING_APP); return ( diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx index fcb018348b72..91a8b94537ab 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.tsx @@ -70,31 +70,31 @@ function AddressPage({privatePersonalDetails, route, isLoadingApp = true}: Addre }, [address]); const handleAddressChange = useCallback((value: unknown, key: unknown) => { - const countryValue = value as Country | ''; - const addressKey = key as keyof Address; + const addressPart = value as string; + const addressPartKey = key as keyof Address; - if (addressKey !== 'country' && addressKey !== 'state' && addressKey !== 'city' && addressKey !== 'zipPostCode') { + if (addressPartKey !== 'country' && addressPartKey !== 'state' && addressPartKey !== 'city' && addressPartKey !== 'zipPostCode') { return; } - if (addressKey === 'country') { - setCurrentCountry(countryValue); + if (addressPartKey === 'country') { + setCurrentCountry(addressPart as Country | ''); setState(''); setCity(''); setZipcode(''); return; } - if (addressKey === 'state') { - setState(countryValue); + if (addressPartKey === 'state') { + setState(addressPart); setCity(''); setZipcode(''); return; } - if (addressKey === 'city') { - setCity(countryValue); + if (addressPartKey === 'city') { + setCity(addressPart); setZipcode(''); return; } - setZipcode(countryValue); + setZipcode(addressPart); }, []); useEffect(() => { diff --git a/src/types/utils/isLoadingOnyxValue.ts b/src/types/utils/isLoadingOnyxValue.ts index 052c97ad40ef..7d5bfa88ba2e 100644 --- a/src/types/utils/isLoadingOnyxValue.ts +++ b/src/types/utils/isLoadingOnyxValue.ts @@ -1,6 +1,6 @@ -import type {OnyxKey, UseOnyxResult} from 'react-native-onyx'; +import type {ResultMetadata} from 'react-native-onyx'; -function isLoadingOnyxValue(...results: Array[1]>): boolean { +function isLoadingOnyxValue(...results: ResultMetadata[]): boolean { return results.some((result) => result.status === 'loading'); } diff --git a/src/utils/arraysEqual.ts b/src/utils/arraysEqual.ts new file mode 100644 index 000000000000..3a8111cc7bb7 --- /dev/null +++ b/src/utils/arraysEqual.ts @@ -0,0 +1,25 @@ +function arraysEqual(a: T[], b: T[]): boolean { + if (a === b) { + return true; + } + if (a == null || b == null) { + return false; + } + if (a.length !== b.length) { + return false; + } + + // If you don't care about the order of the elements inside + // the array, you should sort both arrays here. + // Please note that calling sort on an array will modify that array. + // you might want to clone your array first. + + for (let i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +export default arraysEqual; diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index ac320729b2b7..a7aad61a4ab3 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -464,7 +464,7 @@ describe('actions/IOU', () => { let newTransaction: OnyxEntry; mockFetch?.pause?.(); return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, chatReport) - .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, iouReport)) + .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, iouReport ?? null)) .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, { [createdAction.reportActionID]: createdAction, @@ -780,7 +780,7 @@ describe('actions/IOU', () => { .then( () => new Promise((resolve) => { - ReportActions.clearAllRelatedReportActionErrors(iouReportID ?? '', iouAction); + ReportActions.clearAllRelatedReportActionErrors(iouReportID ?? '', iouAction ?? null); resolve(); }), )