From 48d422ae495d134a953de5a050d8daf59f21dc45 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 18 Dec 2023 16:21:41 +0700 Subject: [PATCH 1/6] fix: add singleExecution for menu items --- src/components/MenuItemGroup.js | 35 +++++++++++++++++++ .../PersonalDetailsInitialPage.js | 3 ++ 2 files changed, 38 insertions(+) create mode 100644 src/components/MenuItemGroup.js diff --git a/src/components/MenuItemGroup.js b/src/components/MenuItemGroup.js new file mode 100644 index 000000000000..60d27247a45f --- /dev/null +++ b/src/components/MenuItemGroup.js @@ -0,0 +1,35 @@ +import useSingleExecution from '@hooks/useSingleExecution'; +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import React, {Children, cloneElement} from 'react'; + +const propTypes = { + /* Actual content wrapped by this component */ + children: PropTypes.node.isRequired, + + /** Whether or not to use the single execution hook */ + shouldUseSingleExecution: PropTypes.bool, +}; +const defaultProps = { + shouldUseSingleExecution: true +}; + +function MenuItemGroup(props){ + const {isExecuting, singleExecution} = useSingleExecution(); + const arrayChildren = Children.toArray(props.children); + + return <> + {Children.map(arrayChildren, (child, index) => { + return cloneElement(child,{ + ...child.props, + onPress: props.shouldUseSingleExecution ? singleExecution(child.props.onPress) : child.props.onPress + }) + })} + +} + +MenuItemGroup.displayName = 'MenuItemGroup'; +MenuItemGroup.propTypes = propTypes; +MenuItemGroup.defaultProps = defaultProps; + +export default MenuItemGroup; \ No newline at end of file diff --git a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js index cf6887b7e04c..a35ad78836b8 100644 --- a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js +++ b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js @@ -17,6 +17,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import MenuItemGroup from '@components/MenuItemGroup'; const propTypes = { /* Onyx Props */ @@ -77,6 +78,7 @@ function PersonalDetailsInitialPage(props) { {props.translate('privatePersonalDetails.privateDataMessage')} + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS)} /> + )} From b81373faf01130e8f568e2bc3a63141933b28d78 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 18 Dec 2023 16:32:51 +0700 Subject: [PATCH 2/6] disable when is executing --- src/components/MenuItemGroup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MenuItemGroup.js b/src/components/MenuItemGroup.js index 60d27247a45f..4fe5adf989b6 100644 --- a/src/components/MenuItemGroup.js +++ b/src/components/MenuItemGroup.js @@ -1,5 +1,4 @@ import useSingleExecution from '@hooks/useSingleExecution'; -import _ from 'lodash'; import PropTypes from 'prop-types'; import React, {Children, cloneElement} from 'react'; @@ -22,7 +21,8 @@ function MenuItemGroup(props){ {Children.map(arrayChildren, (child, index) => { return cloneElement(child,{ ...child.props, - onPress: props.shouldUseSingleExecution ? singleExecution(child.props.onPress) : child.props.onPress + onPress: props.shouldUseSingleExecution ? singleExecution(child.props.onPress) : child.props.onPress, + disabled: child.props.disabled || isExecuting }) })} From d2f5a1d8c92fa2443dc6e9f7ca4ea91b7aa1643f Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 22 Jan 2024 01:42:49 +0700 Subject: [PATCH 3/6] context approach --- src/components/MenuItem.tsx | 14 +++++-- src/components/MenuItemGroup.js | 35 ---------------- src/components/MenuItemGroup.tsx | 30 ++++++++++++++ .../PersonalDetailsInitialPage.js | 40 +++++++++---------- 4 files changed, 61 insertions(+), 58 deletions(-) delete mode 100644 src/components/MenuItemGroup.js create mode 100644 src/components/MenuItemGroup.tsx diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 334fa9895205..31c07724975a 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -1,7 +1,7 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import type {ImageContentFit} from 'expo-image'; import type {ForwardedRef, ReactNode} from 'react'; -import React, {forwardRef, useEffect, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {AnimatedStyle} from 'react-native-reanimated'; @@ -14,6 +14,8 @@ import ControlSelection from '@libs/ControlSelection'; import convertToLTR from '@libs/convertToLTR'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; +// import useWaitForNavigation from '@hooks/useWaitForNavigation'; +import Navigation from '@libs/Navigation/Navigation'; import type {AvatarSource} from '@libs/UserUtils'; import variables from '@styles/variables'; import * as Session from '@userActions/Session'; @@ -29,6 +31,7 @@ import Hoverable from './Hoverable'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import * as defaultWorkspaceAvatars from './Icon/WorkspaceDefaultAvatars'; +import {MenuItemGroupContext} from './MenuItemGroup'; import MultipleAvatars from './MultipleAvatars'; import PressableWithSecondaryInteraction from './PressableWithSecondaryInteraction'; import RenderHTML from './RenderHTML'; @@ -295,6 +298,7 @@ function MenuItem( const {isSmallScreenWidth} = useWindowDimensions(); const [html, setHtml] = useState(''); const titleRef = useRef(''); + const {isExecuting, singleExecution, waitForNavigate} = useContext(MenuItemGroupContext) ?? {}; const isDeleted = style && Array.isArray(style) ? style.includes(styles.offlineFeedback.deleted) : false; const descriptionVerticalMargin = shouldShowDescriptionOnTop ? styles.mb1 : styles.mt1; @@ -370,7 +374,11 @@ function MenuItem( } if (onPress && event) { - onPress(event); + if (!singleExecution || !waitForNavigate) { + onPress(event); + return; + } + singleExecution(waitForNavigate(() => onPress(event))); } }; @@ -394,7 +402,7 @@ function MenuItem( shouldGreyOutWhenDisabled && disabled && styles.buttonOpacityDisabled, ] as StyleProp } - disabled={disabled} + disabled={disabled || isExecuting} ref={ref} role={CONST.ROLE.MENUITEM} accessibilityLabel={title ? title.toString() : ''} diff --git a/src/components/MenuItemGroup.js b/src/components/MenuItemGroup.js deleted file mode 100644 index 4fe5adf989b6..000000000000 --- a/src/components/MenuItemGroup.js +++ /dev/null @@ -1,35 +0,0 @@ -import useSingleExecution from '@hooks/useSingleExecution'; -import PropTypes from 'prop-types'; -import React, {Children, cloneElement} from 'react'; - -const propTypes = { - /* Actual content wrapped by this component */ - children: PropTypes.node.isRequired, - - /** Whether or not to use the single execution hook */ - shouldUseSingleExecution: PropTypes.bool, -}; -const defaultProps = { - shouldUseSingleExecution: true -}; - -function MenuItemGroup(props){ - const {isExecuting, singleExecution} = useSingleExecution(); - const arrayChildren = Children.toArray(props.children); - - return <> - {Children.map(arrayChildren, (child, index) => { - return cloneElement(child,{ - ...child.props, - onPress: props.shouldUseSingleExecution ? singleExecution(child.props.onPress) : child.props.onPress, - disabled: child.props.disabled || isExecuting - }) - })} - -} - -MenuItemGroup.displayName = 'MenuItemGroup'; -MenuItemGroup.propTypes = propTypes; -MenuItemGroup.defaultProps = defaultProps; - -export default MenuItemGroup; \ No newline at end of file diff --git a/src/components/MenuItemGroup.tsx b/src/components/MenuItemGroup.tsx new file mode 100644 index 000000000000..995efc962921 --- /dev/null +++ b/src/components/MenuItemGroup.tsx @@ -0,0 +1,30 @@ +import React, {createContext} from 'react'; +import useSingleExecution from '@hooks/useSingleExecution'; +import type {Action} from '@hooks/useSingleExecution'; +import useWaitForNavigation from '@hooks/useWaitForNavigation'; + +type MenuItemGroupContextProps = { + isExecuting: boolean; + singleExecution: (action?: Action | undefined) => (...params: T) => void; + waitForNavigate: ReturnType; +}; + +const MenuItemGroupContext = createContext(undefined); + +interface MenuItemGroupProps { + /* Actual content wrapped by this component */ + children: React.ReactNode; + + /** Whether or not to use the single execution hook */ + shouldUseSingleExecution: boolean; +} + +const MenuItemGroup: React.FC = ({children}) => { + const {isExecuting, singleExecution} = useSingleExecution(); + const waitForNavigate = useWaitForNavigation(); + + return {children}; +}; + +export {MenuItemGroupContext}; +export default MenuItemGroup; diff --git a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js index a35ad78836b8..ee633cbaff1d 100644 --- a/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js +++ b/src/pages/settings/Profile/PersonalDetails/PersonalDetailsInitialPage.js @@ -5,6 +5,7 @@ import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MenuItemGroup from '@components/MenuItemGroup'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import {withNetwork} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -17,7 +18,6 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import MenuItemGroup from '@components/MenuItemGroup'; const propTypes = { /* Onyx Props */ @@ -79,25 +79,25 @@ function PersonalDetailsInitialPage(props) { {props.translate('privatePersonalDetails.privateDataMessage')} - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_LEGAL_NAME)} - /> - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH)} - titleStyle={[styles.flex1]} - /> - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS)} - /> + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_LEGAL_NAME)} + /> + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH)} + titleStyle={[styles.flex1]} + /> + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS)} + /> From ddffc2531e8c658771d80ff4ea2b6fbc001a1311 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 22 Jan 2024 01:43:36 +0700 Subject: [PATCH 4/6] remove redundant imports --- src/components/MenuItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 31c07724975a..e701c9e21c00 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -14,8 +14,6 @@ import ControlSelection from '@libs/ControlSelection'; import convertToLTR from '@libs/convertToLTR'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; -// import useWaitForNavigation from '@hooks/useWaitForNavigation'; -import Navigation from '@libs/Navigation/Navigation'; import type {AvatarSource} from '@libs/UserUtils'; import variables from '@styles/variables'; import * as Session from '@userActions/Session'; From 5b440b7ae7db307cdf559f96064c05341051e8dd Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 22 Jan 2024 01:50:23 +0700 Subject: [PATCH 5/6] fix lint --- src/components/MenuItemGroup.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/MenuItemGroup.tsx b/src/components/MenuItemGroup.tsx index 995efc962921..e8c7a62bf1a6 100644 --- a/src/components/MenuItemGroup.tsx +++ b/src/components/MenuItemGroup.tsx @@ -1,4 +1,4 @@ -import React, {createContext} from 'react'; +import React, {createContext, useMemo} from 'react'; import useSingleExecution from '@hooks/useSingleExecution'; import type {Action} from '@hooks/useSingleExecution'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; @@ -11,20 +11,25 @@ type MenuItemGroupContextProps = { const MenuItemGroupContext = createContext(undefined); -interface MenuItemGroupProps { +type MenuItemGroupProps = { /* Actual content wrapped by this component */ children: React.ReactNode; /** Whether or not to use the single execution hook */ shouldUseSingleExecution: boolean; -} +}; -const MenuItemGroup: React.FC = ({children}) => { +function MenuItemGroup({children, shouldUseSingleExecution = true}: MenuItemGroupProps) { const {isExecuting, singleExecution} = useSingleExecution(); const waitForNavigate = useWaitForNavigation(); - return {children}; -}; + const value = useMemo( + () => (shouldUseSingleExecution ? {isExecuting, singleExecution, waitForNavigate} : undefined), + [shouldUseSingleExecution, isExecuting, singleExecution, waitForNavigate], + ); + + return {children}; +} export {MenuItemGroupContext}; export default MenuItemGroup; From f96ae97de7e196fae79a26b8cdb205c1bb0f360e Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 22 Jan 2024 02:01:12 +0700 Subject: [PATCH 6/6] execute singleExecution --- src/components/MenuItem.tsx | 2 +- src/components/MenuItemGroup.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index e701c9e21c00..f009d3f83939 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -376,7 +376,7 @@ function MenuItem( onPress(event); return; } - singleExecution(waitForNavigate(() => onPress(event))); + singleExecution(waitForNavigate(() => onPress(event)))(); } }; diff --git a/src/components/MenuItemGroup.tsx b/src/components/MenuItemGroup.tsx index e8c7a62bf1a6..8dc8586028d8 100644 --- a/src/components/MenuItemGroup.tsx +++ b/src/components/MenuItemGroup.tsx @@ -16,7 +16,7 @@ type MenuItemGroupProps = { children: React.ReactNode; /** Whether or not to use the single execution hook */ - shouldUseSingleExecution: boolean; + shouldUseSingleExecution?: boolean; }; function MenuItemGroup({children, shouldUseSingleExecution = true}: MenuItemGroupProps) {