Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
b13d7bb
Refactor FloatingActionButtonAndPopover into composable pieces
TMisiukiewicz Feb 23, 2026
f3e1f87
extract platform dragover dismiss into platform-specific hook
TMisiukiewicz Feb 23, 2026
c604841
remove data intermediary — hooks fetch own session/policy/transaction…
TMisiukiewicz Feb 23, 2026
9d65f70
use selector on REPORT collection to avoid full collection re-renders
TMisiukiewicz Feb 23, 2026
8beca63
add justification comments to eslint-disable lines
TMisiukiewicz Feb 23, 2026
2224d4d
remove useMemo from modified files
TMisiukiewicz Feb 23, 2026
538804d
convert FAB menu hooks to JSX components with registry pattern
TMisiukiewicz Feb 23, 2026
6b5705c
delete old use*MenuItem hooks replaced by JSX components
TMisiukiewicz Feb 23, 2026
eeeb56d
refactor FAB menu to RC-compliant JSX architecture with FABPopoverMenu
TMisiukiewicz Feb 23, 2026
88ef1b2
fix FABPopoverMenu mobile layout - use flexGrow1 on small screens
TMisiukiewicz Feb 23, 2026
c250d47
fix RC violations in useScanActions - drop useCallback/useRef, use us…
TMisiukiewicz Feb 23, 2026
b551ced
drop useCallback/useMemo from FAB menu items - let RC handle memoization
TMisiukiewicz Feb 23, 2026
435d558
remove useCallback/useMemo from FABPopoverMenu and useRedirectToExpen…
TMisiukiewicz Feb 23, 2026
a7a0179
remove shouldUseNarrowLayout prop drilling from FAB popover components
TMisiukiewicz Feb 24, 2026
d9718e0
move visibility logic inside menu items, drop React.Children.toArray …
TMisiukiewicz Feb 24, 2026
9549c0f
move icon loading inside each menu item, remove icons prop and MenuIt…
TMisiukiewicz Feb 24, 2026
9ff0839
internalize anchorPosition inside FABPopoverMenu
TMisiukiewicz Feb 24, 2026
6a8806e
internalize fromSidebarMediumScreen in FABPopoverMenu
TMisiukiewicz Feb 24, 2026
afc0d0b
extract FAB_MENU_ITEM_IDS constants and replace hardcoded strings
TMisiukiewicz Feb 24, 2026
f7fceb0
move FAB_MENU_ITEM_IDS into CONST/index.ts
TMisiukiewicz Feb 24, 2026
feca2a3
extract useFABMenuItem hook from menu item registration boilerplate
TMisiukiewicz Feb 24, 2026
62a0f3d
cleanup FABPopoverMenu
TMisiukiewicz Feb 24, 2026
7a8bfa4
resolve merge conflict — keep refactored FAB popover version
TMisiukiewicz Feb 24, 2026
f730894
extend useFABMenuItem to return isFocused and wrapperStyle
TMisiukiewicz Feb 24, 2026
485d3d1
remove canBeMissing
TMisiukiewicz Feb 24, 2026
92f9247
extend useFABMenuItem to expose setFocusedIndex and onItemPress, elim…
TMisiukiewicz Feb 24, 2026
3ff2e56
extract FABFocusableMenuItem wrapper, eliminating 5 repeated props an…
TMisiukiewicz Feb 24, 2026
c98df63
remove isMenuMounted lazy-mount guard and onModalHide chain
TMisiukiewicz Feb 24, 2026
1268655
collapse FABPopoverContent and FABPopoverContentInner pass-through la…
TMisiukiewicz Feb 24, 2026
63c8296
cleanup useScanActions
TMisiukiewicz Feb 24, 2026
83a45c6
remove allPolicies leak from useRedirectToExpensifyClassic
TMisiukiewicz Feb 24, 2026
dd240d7
simplify closing the menu on losing focus
TMisiukiewicz Feb 24, 2026
b3cc3b1
improve QuickActionMenuItem
TMisiukiewicz Feb 24, 2026
ec55ac9
reuse redirect to expensify classic
TMisiukiewicz Feb 24, 2026
f11caab
address PR comments
TMisiukiewicz Feb 24, 2026
f28760b
prettier
TMisiukiewicz Feb 24, 2026
01a84ae
rewrite useRedirectToExpensifyClassic
TMisiukiewicz Feb 24, 2026
ab102fe
fix ts
TMisiukiewicz Feb 24, 2026
0bc3349
Merge exfy/main into react-patterns/refactor-fab-popover-decomposition
adhorodyski Mar 6, 2026
76d91b3
Review fixes for FAB popover decomposition
adhorodyski Mar 6, 2026
e4cb850
fix: compilation errors
adhorodyski Mar 9, 2026
36c9e5b
fix missing react import
adhorodyski Mar 9, 2026
2191d94
Replace inlined selectors with existing ones from src/selectors/
adhorodyski Mar 9, 2026
7d7150b
Replace IIFE with inline assignment for isTravelEnabled
adhorodyski Mar 9, 2026
f17770b
Extract FAB buttons into self-contained FABButtons wrapper
adhorodyski Mar 9, 2026
7a06742
fix prettier
adhorodyski Mar 9, 2026
3846341
Merge remote-tracking branch 'origin/main' into react-patterns/refact…
TMisiukiewicz Mar 10, 2026
fb048fa
Merge remote-tracking branch 'exfy/main' into react-patterns/refactor…
adhorodyski Mar 10, 2026
abcb893
Fix deprecated MutableRefObject usage in useCreateEmptyReportConfirma…
adhorodyski Mar 10, 2026
b3a0ac3
Merge main into react-patterns/refactor-fab-popover-decomposition
TMisiukiewicz Mar 11, 2026
a1baa97
Fix confirmation modal not dismissing when clicking reports link
adhorodyski Mar 11, 2026
c953885
Add ScanShortcut telemetry span to startQuickScan
adhorodyski Mar 11, 2026
3b787df
Merge exfy/main into react-patterns/refactor-fab-popover-decomposition
adhorodyski Mar 12, 2026
efdba36
Address PR review comments
adhorodyski Mar 12, 2026
1ebde66
Add unit tests for useFABMenuItem and useConditionalCreateEmptyReport…
adhorodyski Mar 13, 2026
c3b3236
Merge exfy/main into react-patterns/refactor-fab-popover-decomposition
adhorodyski Mar 16, 2026
3a98ff8
Make useDragoverDismiss one-shot to avoid redundant state updates
adhorodyski Mar 16, 2026
753f7f2
Merge branch 'Expensify:main' into react-patterns/refactor-fab-popove…
adhorodyski Mar 17, 2026
b359de5
Merge exfy/main into react-patterns/refactor-fab-popover-decomposition
adhorodyski Mar 17, 2026
85ed362
Merge branch 'react-patterns/refactor-fab-popover-decomposition' of h…
adhorodyski Mar 17, 2026
66f052f
Merge exfy/main into react-patterns/refactor-fab-popover-decomposition
adhorodyski Mar 17, 2026
ce848a5
Restore useful code comments removed during FAB decomposition
adhorodyski Mar 17, 2026
2748220
Fix double vertical padding on FAB popover menu (wide layout)
adhorodyski Mar 17, 2026
ce9f79d
Fix Quick Action position — move to bottom of FAB menu
adhorodyski Mar 17, 2026
d3edd73
Merge branch 'Expensify:main' into react-patterns/refactor-fab-popove…
adhorodyski Mar 17, 2026
50f6f92
Merge remote-tracking branch 'exfy/main' into react-patterns/refactor…
adhorodyski Mar 17, 2026
83cded2
Pass userBillingGraceEndPeriods from useOnyx in decomposed FAB compon…
adhorodyski Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1736,6 +1736,17 @@ const CONST = {
FAB_OUT: 200,
},
},
FAB_MENU_ITEM_IDS: {
QUICK_ACTION: 'quick-action',
EXPENSE: 'expense',
TRACK_DISTANCE: 'track-distance',
CREATE_REPORT: 'create-report',
NEW_CHAT: 'new-chat',
INVOICE: 'invoice',
TRAVEL: 'travel',
TEST_DRIVE: 'test-drive',
NEW_WORKSPACE: 'new-workspace',
},
TIMING: {
REPORT_ACTION_ITEM_LAYOUT_DEBOUNCE_TIME: 1500,
SHOW_LOADING_SPINNER_DEBOUNCE_TIME: 250,
Expand Down
30 changes: 13 additions & 17 deletions src/components/FloatingCameraButton/BaseFloatingCameraButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useCallback, useMemo} from 'react';
import React, {useState} from 'react';
import {View} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {OnyxCollection} from 'react-native-onyx';
import Icon from '@components/Icon';
import {PressableWithoutFeedback} from '@components/Pressable';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -18,13 +18,12 @@ import Tab from '@userActions/Tab';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {sessionEmailAndAccountIDSelector} from '@src/selectors/Session';
import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft';
import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type IconAsset from '@src/types/utils/IconAsset';

const sessionSelector = (session: OnyxEntry<OnyxTypes.Session>) => ({email: session?.email, accountID: session?.accountID});

type BaseFloatingCameraButtonProps = {
icon: IconAsset;
};
Expand All @@ -36,23 +35,20 @@ function BaseFloatingCameraButton({icon}: BaseFloatingCameraButtonProps) {

const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`);
const [session] = useOnyx(ONYXKEYS.SESSION, {selector: sessionSelector});
const [session] = useOnyx(ONYXKEYS.SESSION, {selector: sessionEmailAndAccountIDSelector});
const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector});
const [userBillingGraceEndPeriods] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
const reportID = useMemo(() => generateReportID(), []);
const [reportID] = useState(() => generateReportID());

const policyChatForActivePolicySelector = useCallback(
(reports: OnyxCollection<OnyxTypes.Report>) => {
if (isEmptyObject(activePolicy) || !activePolicy?.isPolicyExpenseChatEnabled) {
return undefined;
}
const policyChatsForActivePolicy = getWorkspaceChats(activePolicyID, [session?.accountID ?? CONST.DEFAULT_NUMBER_ID], reports);
return policyChatsForActivePolicy.at(0);
},
[activePolicy, activePolicyID, session?.accountID],
);
const [policyChatForActivePolicy] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: policyChatForActivePolicySelector}, [policyChatForActivePolicySelector]);
const policyChatForActivePolicySelector = (reports: OnyxCollection<OnyxTypes.Report>) => {
if (isEmptyObject(activePolicy) || !activePolicy?.isPolicyExpenseChatEnabled) {
return undefined;
}
const policyChatsForActivePolicy = getWorkspaceChats(activePolicyID, [session?.accountID ?? CONST.DEFAULT_NUMBER_ID], reports);
return policyChatsForActivePolicy.at(0);
};
const [policyChatForActivePolicy] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: policyChatForActivePolicySelector});

const onPress = () => {
interceptAnonymousUser(() => {
Expand Down
67 changes: 32 additions & 35 deletions src/components/Navigation/QuickCreationActionsBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ function QuickCreationActionsBar() {
[currentUserPersonalDetails, hasViolations, defaultChatEnabledPolicy, isASAPSubmitBetaEnabled, allBetas],
);

const {openCreateReportConfirmation, CreateReportConfirmationModal} = useCreateEmptyReportConfirmation({
const {openCreateReportConfirmation} = useCreateEmptyReportConfirmation({
policyID: defaultChatEnabledPolicyID,
policyName: defaultChatEnabledPolicy?.name ?? '',
onConfirm: handleCreateWorkspaceReport,
Expand Down Expand Up @@ -229,45 +229,42 @@ function QuickCreationActionsBar() {
);

return (
<>
{CreateReportConfirmationModal}
<View style={[styles.flexRow, styles.gap2, styles.pt1, styles.pb5]}>
<View style={[styles.flexRow, styles.gap2, styles.pt1, styles.pb5]}>
<Button
small
icon={icons.ReceiptPlus}
text={translate('common.expense')}
onPress={handleExpense}
style={styles.quickCreationActionsBarButton}
textStyles={styles.quickCreationActionsBarButtonText}
/>
<Button
small
icon={icons.DocumentPlus}
text={translate('common.report')}
onPress={handleReport}
style={styles.quickCreationActionsBarButton}
textStyles={styles.quickCreationActionsBarButtonText}
/>
<Button
small
icon={icons.CarPlus}
text={translate('common.distance')}
onPress={handleDistance}
style={styles.quickCreationActionsBarButton}
textStyles={styles.quickCreationActionsBarButtonText}
/>
{shouldShowBookTravel && (
<Button
small
icon={icons.ReceiptPlus}
text={translate('common.expense')}
onPress={handleExpense}
icon={icons.LuggageWithLines}
text={translate('travel.bookTravel')}
onPress={handleBookTravel}
style={styles.quickCreationActionsBarButton}
textStyles={styles.quickCreationActionsBarButtonText}
/>
<Button
small
icon={icons.DocumentPlus}
text={translate('common.report')}
onPress={handleReport}
style={styles.quickCreationActionsBarButton}
textStyles={styles.quickCreationActionsBarButtonText}
/>
<Button
small
icon={icons.CarPlus}
text={translate('common.distance')}
onPress={handleDistance}
style={styles.quickCreationActionsBarButton}
textStyles={styles.quickCreationActionsBarButtonText}
/>
{shouldShowBookTravel && (
<Button
small
icon={icons.LuggageWithLines}
text={translate('travel.bookTravel')}
onPress={handleBookTravel}
style={styles.quickCreationActionsBarButton}
textStyles={styles.quickCreationActionsBarButtonText}
/>
)}
</View>
</>
)}
</View>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function SearchFiltersBarCreateButton() {
[currentUserPersonalDetails, hasViolations, defaultChatEnabledPolicy, isASAPSubmitBetaEnabled, allBetas],
);

const {openCreateReportConfirmation, CreateReportConfirmationModal} = useCreateEmptyReportConfirmation({
const {openCreateReportConfirmation} = useCreateEmptyReportConfirmation({
policyID: defaultChatEnabledPolicyID,
policyName: defaultChatEnabledPolicy?.name ?? '',
onConfirm: handleCreateWorkspaceReport,
Expand Down Expand Up @@ -226,7 +226,6 @@ function SearchFiltersBarCreateButton() {

return (
<View style={[styles.pr5, styles.searchFiltersBarCreateButton]}>
{CreateReportConfirmationModal}
<PopoverMenu
onClose={hideCreateMenu}
isVisible={isCreateMenuActive}
Expand Down
6 changes: 1 addition & 5 deletions src/hooks/useConditionalCreateEmptyReportConfirmation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {useCallback, useMemo} from 'react';
import type {ReactNode} from 'react';
import ONYXKEYS from '@src/ONYXKEYS';
import useCreateEmptyReportConfirmation from './useCreateEmptyReportConfirmation';
import useHasEmptyReportsForPolicy from './useHasEmptyReportsForPolicy';
Expand All @@ -23,8 +22,6 @@ type UseConditionalCreateEmptyReportConfirmationResult = {
handleCreateReport: () => void;
/** Whether an empty report already exists for the provided policy */
hasEmptyReport: boolean;
/** The confirmation modal React component to render */
CreateReportConfirmationModal: ReactNode;
};

/**
Expand All @@ -49,7 +46,7 @@ export default function useConditionalCreateEmptyReportConfirmation({
[onCreateReport],
);

const {openCreateReportConfirmation, CreateReportConfirmationModal} = useCreateEmptyReportConfirmation({
const {openCreateReportConfirmation} = useCreateEmptyReportConfirmation({
policyID,
policyName,
onConfirm: handleReportCreationConfirmed,
Expand All @@ -68,6 +65,5 @@ export default function useConditionalCreateEmptyReportConfirmation({
return {
handleCreateReport,
hasEmptyReport,
CreateReportConfirmationModal,
};
}
146 changes: 61 additions & 85 deletions src/hooks/useCreateEmptyReportConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type {ReactNode} from 'react';
import React, {useCallback, useMemo, useState} from 'react';
import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import CheckboxWithLabel from '@components/CheckboxWithLabel';
import ConfirmModal from '@components/ConfirmModal';
import {ModalActions} from '@components/Modal/Global/ModalContext';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import Navigation from '@libs/Navigation/Navigation';
import {buildCannedSearchQuery} from '@libs/SearchQueryUtils';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import useConfirmModal from './useConfirmModal';
import useLocalize from './useLocalize';
import useThemeStyles from './useThemeStyles';

Expand All @@ -26,100 +26,76 @@ type UseCreateEmptyReportConfirmationParams = {
type UseCreateEmptyReportConfirmationResult = {
/** Function to open the confirmation modal */
openCreateReportConfirmation: () => void;
/** The confirmation modal React component to render */
CreateReportConfirmationModal: ReactNode;
};

/**
* A React hook that provides a confirmation modal for creating empty reports.
* When a user attempts to create a new report in a workspace where they already have an empty report,
* this hook displays a confirmation modal to prevent accidental duplicate empty reports.
*
* @param params - Configuration object for the hook
* @param params.policyName - The display name of the policy/workspace
* @param params.onConfirm - Callback function to execute when user confirms report creation
* @returns An object containing:
* - openCreateReportConfirmation: Function to open the confirmation modal
* - CreateReportConfirmationModal: The confirmation modal React component to render
*
* @example
* const {openCreateReportConfirmation, CreateReportConfirmationModal} = useCreateEmptyReportConfirmation({
* policyID: 'policy123',
* policyName: 'Engineering Team',
* onConfirm: handleCreateReport,
* });
*
*/
export default function useCreateEmptyReportConfirmation({policyName, onConfirm, onCancel}: UseCreateEmptyReportConfirmationParams): UseCreateEmptyReportConfirmationResult {
function ConfirmationPrompt({workspaceName, checkboxRef, onLinkPress}: {workspaceName: string; checkboxRef: React.RefObject<boolean>; onLinkPress: () => void}) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const workspaceDisplayName = useMemo(() => (policyName?.trim().length ? policyName : translate('report.newReport.genericWorkspaceName')), [policyName, translate]);
const [isVisible, setIsVisible] = useState(false);
const [modalWorkspaceName, setModalWorkspaceName] = useState(workspaceDisplayName);
const [shouldDismissEmptyReportsConfirmation, setShouldDismissEmptyReportsConfirmation] = useState(false);
const [isChecked, setIsChecked] = useState(false);

return (
<View style={styles.gap4}>
<Text>
{translate('report.newReport.emptyReportConfirmationPrompt', {workspaceName})}{' '}
<TextLink onPress={onLinkPress}>{translate('report.newReport.emptyReportConfirmationPromptLink')}.</TextLink>
</Text>
<CheckboxWithLabel
accessibilityLabel={translate('report.newReport.emptyReportConfirmationDontShowAgain')}
label={translate('report.newReport.emptyReportConfirmationDontShowAgain')}
isChecked={isChecked}
onInputChange={(value) => {
const checked = !!value;
setIsChecked(checked);
// eslint-disable-next-line no-param-reassign
checkboxRef.current = checked;
}}
/>
</View>
);
}

const handleConfirm = useCallback(() => {
onConfirm(shouldDismissEmptyReportsConfirmation);
setShouldDismissEmptyReportsConfirmation(false);
setIsVisible(false);
}, [onConfirm, shouldDismissEmptyReportsConfirmation]);
export default function useCreateEmptyReportConfirmation({policyName, onConfirm, onCancel}: UseCreateEmptyReportConfirmationParams): UseCreateEmptyReportConfirmationResult {
const {translate} = useLocalize();
const {showConfirmModal, closeModal} = useConfirmModal();
const workspaceDisplayName = policyName?.trim().length ? policyName : translate('report.newReport.genericWorkspaceName');

const handleCancel = useCallback(() => {
onCancel?.();
setShouldDismissEmptyReportsConfirmation(false);
setIsVisible(false);
}, [onCancel]);
const onConfirmRef = useRef(onConfirm);
const onCancelRef = useRef(onCancel);
useEffect(() => {
onConfirmRef.current = onConfirm;
onCancelRef.current = onCancel;
}, [onConfirm, onCancel]);

const handleReportsLinkPress = useCallback(() => {
onCancel?.();
setShouldDismissEmptyReportsConfirmation(false);
setIsVisible(false);
Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT})}));
}, [onCancel]);
const openCreateReportConfirmation = () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Memoize openCreateReportConfirmation

openCreateReportConfirmation is recreated on every render because it is a plain function, but callers like useSearchTypeMenuSections and NewReportWorkspaceSelectionPage invoke it from useEffect(..., [pending..., openCreateReportConfirmation]). When pending state is set, this opens the modal, which updates modal context and rerenders; the changed function identity retriggers the effect and opens the same modal again, causing repeated/stacked confirmations. This should be wrapped in useCallback with stable dependencies.

Useful? React with 👍 / 👎.

const checkboxRef = {current: false};

const openCreateReportConfirmation = useCallback(() => {
// The caller is responsible for determining if empty report confirmation
// should be shown. We simply open the modal when called.
setModalWorkspaceName(workspaceDisplayName);
setShouldDismissEmptyReportsConfirmation(false);
setIsVisible(true);
}, [workspaceDisplayName]);
const handleLinkPress = () => {
closeModal();
Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT})}));
Comment on lines +72 to +74

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Call onCancel when navigating from confirmation link

The link handler inside the confirmation prompt closes the modal and navigates, but it no longer triggers onCancel. Before this refactor, link clicks executed the cancel callback, and current callers rely on onCancel to clear pending state (pendingReportCreation / pendingPolicySelection); skipping it can leave stale pending state after navigating to Search and cause incorrect follow-up behavior when returning.

Useful? React with 👍 / 👎.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo Claude is right here, this is happening but implicitly in the promise chain.


The review comment is invalid.

The bot claims onCancel is no longer called when the link is pressed, but tracing the code shows it IS called:

  1. handleLinkPress (line 72) calls closeModal()
  2. closeModal() in ModalContext (line 68) defaults to {action: 'CLOSE'} and resolves the modal promise with that payload
  3. Back in useCreateEmptyReportConfirmation (lines 89-94), the .then() handler checks result.action === ModalActions.CONFIRM — since it's 'CLOSE', it falls into the else branch which calls onCancelRef.current?.()

So onCancel is triggered via the promise resolution path. The refactor replaced an explicit onCancel?.() call with an equivalent implicit one through the modal's promise-based API. The behavior is preserved.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};

const prompt = useMemo(
() => (
<View style={styles.gap4}>
<Text>
{translate('report.newReport.emptyReportConfirmationPrompt', {workspaceName: modalWorkspaceName})}{' '}
<TextLink onPress={handleReportsLinkPress}>{translate('report.newReport.emptyReportConfirmationPromptLink')}.</TextLink>
</Text>
<CheckboxWithLabel
accessibilityLabel={translate('report.newReport.emptyReportConfirmationDontShowAgain')}
label={translate('report.newReport.emptyReportConfirmationDontShowAgain')}
isChecked={shouldDismissEmptyReportsConfirmation}
onInputChange={(value) => setShouldDismissEmptyReportsConfirmation(!!value)}
showConfirmModal({
// Adding a space at the end because of this bug in react-native: https://github.com/facebook/react-native/issues/53286
title: `${translate('report.newReport.emptyReportConfirmationTitle')} `,
confirmText: translate('report.newReport.createReport'),
cancelText: translate('common.cancel'),
prompt: (
<ConfirmationPrompt
workspaceName={workspaceDisplayName}
checkboxRef={checkboxRef}
onLinkPress={handleLinkPress}
/>
</View>
),
[handleReportsLinkPress, modalWorkspaceName, shouldDismissEmptyReportsConfirmation, styles.gap4, translate],
);

const CreateReportConfirmationModal = useMemo(
() => (
<ConfirmModal
confirmText={translate('report.newReport.createReport')}
cancelText={translate('common.cancel')}
isVisible={isVisible}
onConfirm={handleConfirm}
onCancel={handleCancel}
prompt={prompt}
title={`${translate('report.newReport.emptyReportConfirmationTitle')} `} // Adding a space at the end because of this bug in react-native: https://github.com/facebook/react-native/issues/53286
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep original comment

/>
),
[handleCancel, handleConfirm, isVisible, prompt, translate],
);
),
}).then((result) => {
if (result.action === ModalActions.CONFIRM) {
onConfirmRef.current(checkboxRef.current);
} else {
onCancelRef.current?.();
}
});
};

return {
openCreateReportConfirmation,
CreateReportConfirmationModal,
};
}
3 changes: 3 additions & 0 deletions src/hooks/useDragoverDismiss/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const useDragoverDismiss: (isActive: boolean, dismiss: () => void) => void = () => {};

export default useDragoverDismiss;
Loading
Loading