From 71c06737e6bd269e0f31631d7e7fe2e54e7b63c0 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 13 Jan 2026 15:21:59 -0800 Subject: [PATCH 1/4] Make QuickBooksDesktopSetupPage follow the rules of react --- src/libs/actions/connections/index.ts | 13 +++ .../qbd/QuickBooksDesktopSetupPage.tsx | 86 +++++++++++++------ 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index 8d123a0de3163..32dde9aa7a8cc 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -295,6 +295,18 @@ function setConnectionError(policyID: string, connectionName: PolicyConnectionNa }); } +function setConnectionErrorMessage(policyID: string, connectionName: PolicyConnectionName, errorMessage?: string) { + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + connections: { + [connectionName]: { + lastSync: { + errorMessage, + }, + }, + }, + }); +} + function copyExistingPolicyConnection(connectedPolicyID: string, targetPolicyID: string, connectionName: ConnectionName) { let stageInProgress; switch (connectionName) { @@ -357,4 +369,5 @@ export { isConnectionInProgress, hasSynchronizationErrorMessage, setConnectionError, + setConnectionErrorMessage, }; diff --git a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx index 9a0936ee28565..14e0a8cf6933c 100644 --- a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx +++ b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useEffect, useEffectEvent, useState} from 'react'; import {View} from 'react-native'; import ActivityIndicator from '@components/ActivityIndicator'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; @@ -19,7 +19,8 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import {setConnectionError} from '@userActions/connections'; +import useOnyx from '@libs/onyx/hooks/useOnyx'; +import {setConnectionError, setConnectionErrorMessage} from '@userActions/connections'; import {getQuickbooksDesktopCodatSetupLink} from '@userActions/connections/QuickbooksDesktop'; import {enablePolicyTaxes} from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; @@ -28,57 +29,88 @@ import type SCREENS from '@src/SCREENS'; type RequireQuickBooksDesktopModalProps = PlatformStackScreenProps; +type SetupLinkFetchStatus = {status: 'loading'} | {status: 'success'; setupLink: string} | {status: 'error'}; + function RequireQuickBooksDesktopModal({route}: RequireQuickBooksDesktopModalProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {environmentURL} = useEnvironment(); const illustrations = useMemoizedLazyIllustrations(['BrokenMagnifyingGlass', 'LaptopWithSecondScreenSync']); const policyID: string = route.params.policyID; - const [hasError, setHasError] = useState(false); - const [codatSetupLink, setCodatSetupLink] = useState(''); - const hasResultOfFetchingSetupLink = !!codatSetupLink || hasError; + const [setupLinkFetchStatus, setSetupLinkFetchStatus] = useState({status: 'loading'}); + const [connectionError] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + selector: (policy) => policy.connections?.[CONST.POLICY.CONNECTIONS.NAME.QBD]?.lastSync?.errorMessage, + }); + + const isLoading = setupLinkFetchStatus.status === 'loading'; + const shouldShowError = setupLinkFetchStatus.status === 'error'; + const codatSetupLink = setupLinkFetchStatus.status === 'success' ? setupLinkFetchStatus.setupLink : ''; + + const ContentWrapper = isLoading + ? ({children}: React.PropsWithChildren) => {children} + : ({children}: React.PropsWithChildren) => children; + + const setLocalizedConnectionError = useEffectEvent(() => { + setConnectionError(policyID, CONST.POLICY.CONNECTIONS.NAME.QBD, translate('workspace.qbd.setupPage.setupErrorTitle')); + }); + + useEffect(() => { + let shouldIgnoreResponse = false; + + // Since QBD doesn't support Taxes, we should disable them from the LHN when connecting to QBD + enablePolicyTaxes(policyID, false); - const ContentWrapper = hasResultOfFetchingSetupLink - ? ({children}: React.PropsWithChildren) => children - : ({children}: React.PropsWithChildren) => {children}; + setSetupLinkFetchStatus({status: 'loading'}); - const fetchSetupLink = useCallback(() => { - setHasError(false); // eslint-disable-next-line rulesdir/no-thenable-actions-in-views getQuickbooksDesktopCodatSetupLink(policyID).then((response) => { - if (!response?.jsonCode) { + if (shouldIgnoreResponse || !response?.jsonCode) { return; } if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { - setCodatSetupLink(String(response?.setupUrl ?? '')); + setSetupLinkFetchStatus({status: 'success', setupLink: String(response?.setupUrl ?? '')}); } else { - setConnectionError(policyID, CONST.POLICY.CONNECTIONS.NAME.QBD, translate('workspace.qbd.setupPage.setupErrorTitle')); - setHasError(true); + setLocalizedConnectionError(); + setSetupLinkFetchStatus({status: 'error'}); } }); - }, [policyID, translate]); - useEffect(() => { - // Since QBD doesn't support Taxes, we should disable them from the LHN when connecting to QBD - enablePolicyTaxes(policyID, false); + return () => { + shouldIgnoreResponse = true; + }; + }, [policyID]); - fetchSetupLink(); - // disabling this rule, as we want this to run only on the first render - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + // Re-translate the error message when locale changes + useEffect(() => { + if (!connectionError) { + return; + } + setConnectionErrorMessage(policyID, CONST.POLICY.CONNECTIONS.NAME.QBD, translate('workspace.qbd.setupPage.setupErrorTitle')); + }, [connectionError, policyID, translate]); useNetwork({ onReconnect: () => { - if (hasResultOfFetchingSetupLink) { + if (!isLoading) { return; } - fetchSetupLink(); + + // eslint-disable-next-line rulesdir/no-thenable-actions-in-views + getQuickbooksDesktopCodatSetupLink(policyID).then((response) => { + if (!response?.jsonCode) { + return; + } + + if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { + setSetupLinkFetchStatus({status: 'success', setupLink: String(response?.setupUrl ?? '')}); + } else { + setLocalizedConnectionError(); + setSetupLinkFetchStatus({status: 'error'}); + } + }); }, }); - const shouldShowError = hasError; - return ( {translate('workspace.qbd.setupPage.title')} {translate('workspace.qbd.setupPage.body')} - {!hasResultOfFetchingSetupLink ? ( + {isLoading ? ( ) : ( Date: Tue, 13 Jan 2026 16:08:30 -0800 Subject: [PATCH 2/4] Implement solution without useEffectEvent --- .../qbd/QuickBooksDesktopSetupPage.tsx | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx index 14e0a8cf6933c..af4e2d0dd08f2 100644 --- a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx +++ b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useEffectEvent, useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import ActivityIndicator from '@components/ActivityIndicator'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; @@ -19,7 +19,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import useOnyx from '@libs/onyx/hooks/useOnyx'; import {setConnectionError, setConnectionErrorMessage} from '@userActions/connections'; import {getQuickbooksDesktopCodatSetupLink} from '@userActions/connections/QuickbooksDesktop'; import {enablePolicyTaxes} from '@userActions/Policy/Policy'; @@ -38,9 +37,6 @@ function RequireQuickBooksDesktopModal({route}: RequireQuickBooksDesktopModalPro const illustrations = useMemoizedLazyIllustrations(['BrokenMagnifyingGlass', 'LaptopWithSecondScreenSync']); const policyID: string = route.params.policyID; const [setupLinkFetchStatus, setSetupLinkFetchStatus] = useState({status: 'loading'}); - const [connectionError] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - selector: (policy) => policy.connections?.[CONST.POLICY.CONNECTIONS.NAME.QBD]?.lastSync?.errorMessage, - }); const isLoading = setupLinkFetchStatus.status === 'loading'; const shouldShowError = setupLinkFetchStatus.status === 'error'; @@ -50,10 +46,6 @@ function RequireQuickBooksDesktopModal({route}: RequireQuickBooksDesktopModalPro ? ({children}: React.PropsWithChildren) => {children} : ({children}: React.PropsWithChildren) => children; - const setLocalizedConnectionError = useEffectEvent(() => { - setConnectionError(policyID, CONST.POLICY.CONNECTIONS.NAME.QBD, translate('workspace.qbd.setupPage.setupErrorTitle')); - }); - useEffect(() => { let shouldIgnoreResponse = false; @@ -62,7 +54,6 @@ function RequireQuickBooksDesktopModal({route}: RequireQuickBooksDesktopModalPro setSetupLinkFetchStatus({status: 'loading'}); - // eslint-disable-next-line rulesdir/no-thenable-actions-in-views getQuickbooksDesktopCodatSetupLink(policyID).then((response) => { if (shouldIgnoreResponse || !response?.jsonCode) { return; @@ -71,7 +62,7 @@ function RequireQuickBooksDesktopModal({route}: RequireQuickBooksDesktopModalPro if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { setSetupLinkFetchStatus({status: 'success', setupLink: String(response?.setupUrl ?? '')}); } else { - setLocalizedConnectionError(); + setConnectionError(policyID, CONST.POLICY.CONNECTIONS.NAME.QBD, translate('workspace.qbd.setupPage.setupErrorTitle')); setSetupLinkFetchStatus({status: 'error'}); } }); @@ -79,15 +70,7 @@ function RequireQuickBooksDesktopModal({route}: RequireQuickBooksDesktopModalPro return () => { shouldIgnoreResponse = true; }; - }, [policyID]); - - // Re-translate the error message when locale changes - useEffect(() => { - if (!connectionError) { - return; - } - setConnectionErrorMessage(policyID, CONST.POLICY.CONNECTIONS.NAME.QBD, translate('workspace.qbd.setupPage.setupErrorTitle')); - }, [connectionError, policyID, translate]); + }, [policyID, translate]); useNetwork({ onReconnect: () => { @@ -104,7 +87,7 @@ function RequireQuickBooksDesktopModal({route}: RequireQuickBooksDesktopModalPro if (response.jsonCode === CONST.JSON_CODE.SUCCESS) { setSetupLinkFetchStatus({status: 'success', setupLink: String(response?.setupUrl ?? '')}); } else { - setLocalizedConnectionError(); + setConnectionError(policyID, CONST.POLICY.CONNECTIONS.NAME.QBD, translate('workspace.qbd.setupPage.setupErrorTitle')); setSetupLinkFetchStatus({status: 'error'}); } }); From d29d7f62e937ec3dc630dbc200709c9dea18c6ac Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 13 Jan 2026 16:10:12 -0800 Subject: [PATCH 3/4] Remove unused setConnectionErrorMessage --- src/libs/actions/connections/index.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index 32dde9aa7a8cc..8d123a0de3163 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -295,18 +295,6 @@ function setConnectionError(policyID: string, connectionName: PolicyConnectionNa }); } -function setConnectionErrorMessage(policyID: string, connectionName: PolicyConnectionName, errorMessage?: string) { - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - connections: { - [connectionName]: { - lastSync: { - errorMessage, - }, - }, - }, - }); -} - function copyExistingPolicyConnection(connectedPolicyID: string, targetPolicyID: string, connectionName: ConnectionName) { let stageInProgress; switch (connectionName) { @@ -369,5 +357,4 @@ export { isConnectionInProgress, hasSynchronizationErrorMessage, setConnectionError, - setConnectionErrorMessage, }; From 2cb8be180c17c2b71a71570a99046a5a956b2a49 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 13 Jan 2026 16:17:13 -0800 Subject: [PATCH 4/4] Remove unused import --- .../workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx index af4e2d0dd08f2..134e70f6f1495 100644 --- a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx +++ b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx @@ -19,7 +19,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import {setConnectionError, setConnectionErrorMessage} from '@userActions/connections'; +import {setConnectionError} from '@userActions/connections'; import {getQuickbooksDesktopCodatSetupLink} from '@userActions/connections/QuickbooksDesktop'; import {enablePolicyTaxes} from '@userActions/Policy/Policy'; import CONST from '@src/CONST';