From 1d123db6e922cb32fea241e47badba03438812b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sat, 15 Oct 2022 17:20:35 +0200 Subject: [PATCH 01/12] move platform code to specific functions --- .../ScreenWrapper/BaseScreenWrapper.js | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/components/ScreenWrapper/BaseScreenWrapper.js b/src/components/ScreenWrapper/BaseScreenWrapper.js index a80a013a1c66..436c4d6b494a 100644 --- a/src/components/ScreenWrapper/BaseScreenWrapper.js +++ b/src/components/ScreenWrapper/BaseScreenWrapper.js @@ -2,7 +2,7 @@ import {KeyboardAvoidingView, View} from 'react-native'; import React from 'react'; import {SafeAreaInsetsContext} from 'react-native-safe-area-context'; import _ from 'underscore'; -import {withOnyx} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import CONST from '../../CONST'; import KeyboardShortcut from '../../libs/KeyboardShortcut'; import Navigation from '../../libs/Navigation/Navigation'; @@ -29,9 +29,19 @@ class BaseScreenWrapper extends React.Component { } componentDidMount() { + let willAlertModalBecomeVisible = false; + + // TODO: move to lib, note: i think this whole thing with the keyboard could be moved to another place? + this.onyxConnectionId = Onyx.connect({ + key: ONYXKEYS.MODAL, + callback: (object) => { + willAlertModalBecomeVisible = object.willAlertModalBecomeVisible; + }, + }); + const shortcutConfig = CONST.KEYBOARD_SHORTCUTS.ESCAPE; this.unsubscribeEscapeKey = KeyboardShortcut.subscribe(shortcutConfig.shortcutKey, () => { - if (this.props.modal.willAlertModalBecomeVisible) { + if (willAlertModalBecomeVisible) { return; } @@ -51,6 +61,9 @@ class BaseScreenWrapper extends React.Component { if (this.unsubscribeTransitionEnd) { this.unsubscribeTransitionEnd(); } + if (this.onyxConnectionId) { + Onyx.disconnect(this.onyxConnectionId); + } } render() { @@ -106,10 +119,11 @@ BaseScreenWrapper.defaultProps = defaultProps; export default compose( withNavigation, withWindowDimensions, - withOnyx({ - modal: { - key: ONYXKEYS.MODAL, - }, - }), + + // withOnyx({ + // modal: { + // key: ONYXKEYS.MODAL, + // }, + // }), withNetwork(), )(BaseScreenWrapper); From 6816236de3c47e46f0c3afd8cc693a1823978387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sat, 15 Oct 2022 17:51:09 +0200 Subject: [PATCH 02/12] clean code --- src/components/ScreenWrapper/BaseScreenWrapper.js | 15 ++++----------- src/libs/onyxSubscribe.js | 12 ++++++++++++ 2 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 src/libs/onyxSubscribe.js diff --git a/src/components/ScreenWrapper/BaseScreenWrapper.js b/src/components/ScreenWrapper/BaseScreenWrapper.js index 436c4d6b494a..732c98418457 100644 --- a/src/components/ScreenWrapper/BaseScreenWrapper.js +++ b/src/components/ScreenWrapper/BaseScreenWrapper.js @@ -2,7 +2,6 @@ import {KeyboardAvoidingView, View} from 'react-native'; import React from 'react'; import {SafeAreaInsetsContext} from 'react-native-safe-area-context'; import _ from 'underscore'; -import Onyx from 'react-native-onyx'; import CONST from '../../CONST'; import KeyboardShortcut from '../../libs/KeyboardShortcut'; import Navigation from '../../libs/Navigation/Navigation'; @@ -18,6 +17,7 @@ import withWindowDimensions from '../withWindowDimensions'; import ONYXKEYS from '../../ONYXKEYS'; import {withNetwork} from '../OnyxProvider'; import {propTypes, defaultProps} from './propTypes'; +import onyxSubscribe from '../../libs/onyxSubscribe'; class BaseScreenWrapper extends React.Component { constructor(props) { @@ -31,8 +31,7 @@ class BaseScreenWrapper extends React.Component { componentDidMount() { let willAlertModalBecomeVisible = false; - // TODO: move to lib, note: i think this whole thing with the keyboard could be moved to another place? - this.onyxConnectionId = Onyx.connect({ + this.unsubscribeOnyx = onyxSubscribe({ key: ONYXKEYS.MODAL, callback: (object) => { willAlertModalBecomeVisible = object.willAlertModalBecomeVisible; @@ -61,8 +60,8 @@ class BaseScreenWrapper extends React.Component { if (this.unsubscribeTransitionEnd) { this.unsubscribeTransitionEnd(); } - if (this.onyxConnectionId) { - Onyx.disconnect(this.onyxConnectionId); + if (this.unsubscribeOnyx) { + this.unsubscribeOnyx(); } } @@ -119,11 +118,5 @@ BaseScreenWrapper.defaultProps = defaultProps; export default compose( withNavigation, withWindowDimensions, - - // withOnyx({ - // modal: { - // key: ONYXKEYS.MODAL, - // }, - // }), withNetwork(), )(BaseScreenWrapper); diff --git a/src/libs/onyxSubscribe.js b/src/libs/onyxSubscribe.js new file mode 100644 index 000000000000..600d010ed27f --- /dev/null +++ b/src/libs/onyxSubscribe.js @@ -0,0 +1,12 @@ +import Onyx from 'react-native-onyx'; + +/** + * Connect to onyx data. Same params as Onyx.connect(), but returns a function to unsubscribe. + * + * @param {Object} mapping Same as for Onyx.connect() + * @return {function(): void} Unsubscribe callback + */ +export default (mapping) => { + const connectionId = Onyx.connect(mapping); + return () => Onyx.disconnect(connectionId); +}; From 73f74686295bfb53a24a31f6efbddef903d37e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 17 Oct 2022 16:28:00 +0200 Subject: [PATCH 03/12] perf: memo SidebarLinks --- src/pages/home/sidebar/SidebarLinks.js | 58 +++++++++++++------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 11c47f2fd391..80a76dfe6c5f 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -164,36 +164,38 @@ class SidebarLinks extends React.Component { SidebarLinks.propTypes = propTypes; SidebarLinks.defaultProps = defaultProps; -export default compose( - withLocalize, - withCurrentUserPersonalDetails, - withWindowDimensions, - withOnyx({ +export default React.memo( + compose( + withLocalize, + withCurrentUserPersonalDetails, + withWindowDimensions, + withOnyx({ // Note: It is very important that the keys subscribed to here are the same // keys that are subscribed to at the top of SidebarUtils.js. If there was a key missing from here and data was updated // for that key, then there would be no re-render and the options wouldn't reflect the new data because SidebarUtils.getOrderedReportIDs() wouldn't be triggered. // This could be changed if each OptionRowLHN used withOnyx() to connect to the Onyx keys, but if you had 10,000 reports // with 10,000 withOnyx() connections, it would have unknown performance implications. - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - }, - betas: { - key: ONYXKEYS.BETAS, - }, - reportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - }), -)(SidebarLinks); + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + }, + betas: { + key: ONYXKEYS.BETAS, + }, + reportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, + }), + )(SidebarLinks), +); From 424f8e095b8df435847cdcbf9ec01ebead9513ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 19 Oct 2022 10:00:49 +0200 Subject: [PATCH 04/12] Revert "perf: memo SidebarLinks" This reverts commit 2d98ce0f6d44cd371f8bacfb49868f9ba5603b61. --- src/pages/home/sidebar/SidebarLinks.js | 58 +++++++++++++------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 80a76dfe6c5f..11c47f2fd391 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -164,38 +164,36 @@ class SidebarLinks extends React.Component { SidebarLinks.propTypes = propTypes; SidebarLinks.defaultProps = defaultProps; -export default React.memo( - compose( - withLocalize, - withCurrentUserPersonalDetails, - withWindowDimensions, - withOnyx({ +export default compose( + withLocalize, + withCurrentUserPersonalDetails, + withWindowDimensions, + withOnyx({ // Note: It is very important that the keys subscribed to here are the same // keys that are subscribed to at the top of SidebarUtils.js. If there was a key missing from here and data was updated // for that key, then there would be no re-render and the options wouldn't reflect the new data because SidebarUtils.getOrderedReportIDs() wouldn't be triggered. // This could be changed if each OptionRowLHN used withOnyx() to connect to the Onyx keys, but if you had 10,000 reports // with 10,000 withOnyx() connections, it would have unknown performance implications. - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - }, - betas: { - key: ONYXKEYS.BETAS, - }, - reportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - }), - )(SidebarLinks), -); + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + }, + betas: { + key: ONYXKEYS.BETAS, + }, + reportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, + }), +)(SidebarLinks); From bb5e243045f60cb140d61dacfa178cfee0054ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 19 Oct 2022 12:27:56 +0200 Subject: [PATCH 05/12] =?UTF-8?q?refactor:=20moved=20PopoverMenu=20into=20?= =?UTF-8?q?its=20own=20component=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … instead of using memo. From performance perspective that even seems to have slightly improved over the React.memo approach. --- .../SidebarScreen/BaseSidebarScreen.js | 121 +------------ .../sidebar/SidebarScreen/PopoverModal.js | 164 ++++++++++++++++++ src/pages/home/sidebar/SidebarScreen/index.js | 44 ++--- .../sidebar/SidebarScreen/index.native.js | 30 ++-- 4 files changed, 201 insertions(+), 158 deletions(-) create mode 100644 src/pages/home/sidebar/SidebarScreen/PopoverModal.js diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js index e2b92deb3c60..805d31bb5d54 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js @@ -1,56 +1,37 @@ import lodashGet from 'lodash/get'; -import _ from 'underscore'; import React, {Component} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../../../../styles/styles'; import SidebarLinks from '../SidebarLinks'; -import PopoverMenu from '../../../../components/PopoverMenu'; -import FAB from '../../../../components/FAB'; import ScreenWrapper from '../../../../components/ScreenWrapper'; import Navigation from '../../../../libs/Navigation/Navigation'; import ROUTES from '../../../../ROUTES'; import Timing from '../../../../libs/actions/Timing'; import CONST from '../../../../CONST'; -import * as Expensicons from '../../../../components/Icon/Expensicons'; -import Permissions from '../../../../libs/Permissions'; -import * as Policy from '../../../../libs/actions/Policy'; import Performance from '../../../../libs/Performance'; import * as Welcome from '../../../../libs/actions/Welcome'; -import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes'; import withDrawerState from '../../../../components/withDrawerState'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions'; +import compose from '../../../../libs/compose'; const propTypes = { - - /* Callback function when the menu is shown */ - onShowCreateMenu: PropTypes.func, - - /* Callback function before the menu is hidden */ - onHideCreateMenu: PropTypes.func, - /** reportID in the current navigation state */ reportIDFromRoute: PropTypes.string, - ...sidebarPropTypes, + ...windowDimensionsPropTypes, }; + const defaultProps = { - onHideCreateMenu: () => {}, - onShowCreateMenu: () => {}, - ...sidebarDefaultProps, + reportIDFromRoute: '', }; class BaseSidebarScreen extends Component { constructor(props) { super(props); - this.hideCreateMenu = this.hideCreateMenu.bind(this); this.startTimer = this.startTimer.bind(this); this.navigateToSettings = this.navigateToSettings.bind(this); - this.showCreateMenu = this.showCreateMenu.bind(this); - - this.state = { - isCreateMenuActive: false, - }; } componentDidMount() { @@ -61,16 +42,6 @@ class BaseSidebarScreen extends Component { Welcome.show({routes, showCreateMenu: this.showCreateMenu}); } - /** - * Method called when we click the floating action button - */ - showCreateMenu() { - this.setState({ - isCreateMenuActive: true, - }); - this.props.onShowCreateMenu(); - } - /** * Method called when avatar is clicked */ @@ -78,18 +49,6 @@ class BaseSidebarScreen extends Component { Navigation.navigate(ROUTES.SETTINGS); } - /** - * Method called either when: - * Pressing the floating action button to open the CreateMenu modal - * Selecting an item on CreateMenu or closing it by clicking outside of the modal component - */ - hideCreateMenu() { - this.props.onHideCreateMenu(); - this.setState({ - isCreateMenuActive: false, - }); - } - /** * Method called when a pinned chat is selected. */ @@ -99,8 +58,6 @@ class BaseSidebarScreen extends Component { } render() { - // Workspaces are policies with type === 'free' - const workspaces = _.filter(this.props.allPolicies, policy => policy && policy.type === CONST.POLICY.TYPE.FREE); return ( - - Navigation.navigate(ROUTES.NEW_CHAT), - }, - { - icon: Expensicons.Users, - text: this.props.translate('sidebarScreen.newGroup'), - onSelected: () => Navigation.navigate(ROUTES.NEW_GROUP), - }, - ...(Permissions.canUsePolicyRooms(this.props.betas) && workspaces.length ? [ - { - icon: Expensicons.Hashtag, - text: this.props.translate('sidebarScreen.newRoom'), - onSelected: () => Navigation.navigate(ROUTES.WORKSPACE_NEW_ROOM), - }, - ] : []), - ...(Permissions.canUseIOUSend(this.props.betas) ? [ - { - icon: Expensicons.Send, - text: this.props.translate('iou.sendMoney'), - onSelected: () => Navigation.navigate(ROUTES.IOU_SEND), - }, - ] : []), - ...(Permissions.canUseIOU(this.props.betas) ? [ - { - icon: Expensicons.MoneyCircle, - text: this.props.translate('iou.requestMoney'), - onSelected: () => Navigation.navigate(ROUTES.IOU_REQUEST), - }, - ] : []), - ...(Permissions.canUseIOU(this.props.betas) ? [ - { - icon: Expensicons.Receipt, - text: this.props.translate('iou.splitBill'), - onSelected: () => Navigation.navigate(ROUTES.IOU_BILL), - }, - ] : []), - ...(!Policy.isAdminOfFreePolicy(this.props.allPolicies) ? [ - { - icon: Expensicons.NewWorkspace, - iconWidth: 46, - iconHeight: 40, - text: this.props.translate('workspace.new.newWorkspace'), - description: this.props.translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => Policy.createWorkspace(), - }, - ] : []), - ]} - /> )} @@ -191,4 +85,7 @@ class BaseSidebarScreen extends Component { BaseSidebarScreen.propTypes = propTypes; BaseSidebarScreen.defaultProps = defaultProps; -export default withDrawerState(BaseSidebarScreen); +export default compose( + withWindowDimensions, + withDrawerState, +)(BaseSidebarScreen); diff --git a/src/pages/home/sidebar/SidebarScreen/PopoverModal.js b/src/pages/home/sidebar/SidebarScreen/PopoverModal.js new file mode 100644 index 000000000000..19e90de9bdcb --- /dev/null +++ b/src/pages/home/sidebar/SidebarScreen/PopoverModal.js @@ -0,0 +1,164 @@ +import React from 'react'; +import _ from 'underscore'; +import {withOnyx} from 'react-native-onyx'; +import PropTypes from 'prop-types'; +import styles from '../../../../styles/styles'; +import * as Expensicons from '../../../../components/Icon/Expensicons'; +import Navigation from '../../../../libs/Navigation/Navigation'; +import ROUTES from '../../../../ROUTES'; +import Permissions from '../../../../libs/Permissions'; +import * as Policy from '../../../../libs/actions/Policy'; +import PopoverMenu from '../../../../components/PopoverMenu'; +import CONST from '../../../../CONST'; +import FAB from '../../../../components/FAB'; +import compose from '../../../../libs/compose'; +import withLocalize from '../../../../components/withLocalize'; +import withWindowDimensions from '../../../../components/withWindowDimensions'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes'; + +const popoverModalBasePropTypes = { + /* Callback function when the menu is shown */ + onShowCreateMenu: PropTypes.func, + + /* Callback function before the menu is hidden */ + onHideCreateMenu: PropTypes.func, +}; +const propTypes = { + ...popoverModalBasePropTypes, + ...sidebarPropTypes, +}; +const defaultProps = { + onHideCreateMenu: () => {}, + onShowCreateMenu: () => {}, + ...sidebarDefaultProps, +}; + +/** + * Responsible for rendering the {@link PopoverMenu}, and the accompanying + * FAB that can open or close the menu. + */ +class PopoverModal extends React.Component { + constructor(props) { + super(props); + + this.showCreateMenu = this.showCreateMenu.bind(this); + this.hideCreateMenu = this.hideCreateMenu.bind(this); + + this.state = { + isCreateMenuActive: false, + }; + } + + /** + * Method called when we click the floating action button + */ + showCreateMenu() { + this.setState({ + isCreateMenuActive: true, + }); + this.props.onShowCreateMenu(); + } + + /** + * Method called either when: + * Pressing the floating action button to open the CreateMenu modal + * Selecting an item on CreateMenu or closing it by clicking outside of the modal component + */ + hideCreateMenu() { + this.props.onHideCreateMenu(); + this.setState({ + isCreateMenuActive: false, + }); + } + + render() { + // Workspaces are policies with type === 'free' + const workspaces = _.filter(this.props.allPolicies, policy => policy && policy.type === CONST.POLICY.TYPE.FREE); + + return ( + <> + Navigation.navigate(ROUTES.NEW_CHAT), + }, + { + icon: Expensicons.Users, + text: this.props.translate('sidebarScreen.newGroup'), + onSelected: () => Navigation.navigate(ROUTES.NEW_GROUP), + }, + ...(Permissions.canUsePolicyRooms(this.props.betas) && workspaces.length ? [ + { + icon: Expensicons.Hashtag, + text: this.props.translate('sidebarScreen.newRoom'), + onSelected: () => Navigation.navigate(ROUTES.WORKSPACE_NEW_ROOM), + }, + ] : []), + ...(Permissions.canUseIOUSend(this.props.betas) ? [ + { + icon: Expensicons.Send, + text: this.props.translate('iou.sendMoney'), + onSelected: () => Navigation.navigate(ROUTES.IOU_SEND), + }, + ] : []), + ...(Permissions.canUseIOU(this.props.betas) ? [ + { + icon: Expensicons.MoneyCircle, + text: this.props.translate('iou.requestMoney'), + onSelected: () => Navigation.navigate(ROUTES.IOU_REQUEST), + }, + ] : []), + ...(Permissions.canUseIOU(this.props.betas) ? [ + { + icon: Expensicons.Receipt, + text: this.props.translate('iou.splitBill'), + onSelected: () => Navigation.navigate(ROUTES.IOU_BILL), + }, + ] : []), + ...(!Policy.isAdminOfFreePolicy(this.props.allPolicies) ? [ + { + icon: Expensicons.NewWorkspace, + iconWidth: 46, + iconHeight: 40, + text: this.props.translate('workspace.new.newWorkspace'), + description: this.props.translate('workspace.new.getTheExpensifyCardAndMore'), + onSelected: () => Policy.createWorkspace(), + }, + ] : []), + ]} + /> + + + ); + } +} + +PopoverModal.propTypes = propTypes; +PopoverModal.defaultProps = defaultProps; + +export {popoverModalBasePropTypes}; +export default compose( + withLocalize, + withWindowDimensions, + withOnyx({ + allPolicies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + betas: { + key: ONYXKEYS.BETAS, + }, + }), +)(PopoverModal); diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.js index 584bcc49f8e4..46e4c9703133 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.js @@ -1,36 +1,37 @@ import React from 'react'; -import {withOnyx} from 'react-native-onyx'; -import compose from '../../../../libs/compose'; -import withWindowDimensions from '../../../../components/withWindowDimensions'; -import withLocalize from '../../../../components/withLocalize'; -import ONYXKEYS from '../../../../ONYXKEYS'; import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes'; import BaseSidebarScreen from './BaseSidebarScreen'; +import PopoverModal from './PopoverModal'; const SidebarScreen = (props) => { - let baseSidebarScreen = null; + let popoverModal = null; /** * Method create event listener */ const createDragoverListener = () => { - document.addEventListener('dragover', baseSidebarScreen.hideCreateMenu); + document.addEventListener('dragover', popoverModal.hideCreateMenu); }; /** * Method remove event listener. */ const removeDragoverListener = () => { - document.removeEventListener('dragover', baseSidebarScreen.hideCreateMenu); + document.removeEventListener('dragover', popoverModal.hideCreateMenu); }; + return ( - baseSidebarScreen = el} - onShowCreateMenu={createDragoverListener} - onHideCreateMenu={removeDragoverListener} - // eslint-disable-next-line react/jsx-props-no-spreading - {...props} - /> + <> + + popoverModal = el} + onShowCreateMenu={createDragoverListener} + onHideCreateMenu={removeDragoverListener} + /> + ); }; @@ -38,15 +39,4 @@ SidebarScreen.propTypes = sidebarPropTypes; SidebarScreen.defaultProps = sidebarDefaultProps; SidebarScreen.displayName = 'SidebarScreen'; -export default compose( - withLocalize, - withWindowDimensions, - withOnyx({ - allPolicies: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - betas: { - key: ONYXKEYS.BETAS, - }, - }), -)(SidebarScreen); +export default SidebarScreen; diff --git a/src/pages/home/sidebar/SidebarScreen/index.native.js b/src/pages/home/sidebar/SidebarScreen/index.native.js index e2cb2838efe8..37bde3cd2ef1 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.native.js +++ b/src/pages/home/sidebar/SidebarScreen/index.native.js @@ -1,28 +1,20 @@ import React from 'react'; -import {withOnyx} from 'react-native-onyx'; -import compose from '../../../../libs/compose'; -import withWindowDimensions from '../../../../components/withWindowDimensions'; -import withLocalize from '../../../../components/withLocalize'; -import ONYXKEYS from '../../../../ONYXKEYS'; import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes'; import BaseSidebarScreen from './BaseSidebarScreen'; +import PopoverModal from './PopoverModal'; -// eslint-disable-next-line react/jsx-props-no-spreading -const SidebarScreen = props => ; +const SidebarScreen = props => ( + <> + + + +); SidebarScreen.propTypes = sidebarPropTypes; SidebarScreen.defaultProps = sidebarDefaultProps; SidebarScreen.displayName = 'SidebarScreen'; -export default compose( - withLocalize, - withWindowDimensions, - withOnyx({ - allPolicies: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - betas: { - key: ONYXKEYS.BETAS, - }, - }), -)(SidebarScreen); +export default SidebarScreen; From e6c69411d74e30d02ae48b1bdfec89aac9a29429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 19 Oct 2022 12:44:40 +0200 Subject: [PATCH 06/12] clean: prop types --- .../SidebarScreen/BaseSidebarScreen.js | 11 ++------- .../sidebar/SidebarScreen/PopoverModal.js | 24 ++++++++++++------- src/pages/home/sidebar/SidebarScreen/index.js | 3 +-- .../sidebar/SidebarScreen/index.native.js | 3 +-- .../sidebar/SidebarScreen/sidebarPropTypes.js | 24 +++---------------- 5 files changed, 22 insertions(+), 43 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js index 805d31bb5d54..40f8dab3d396 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js @@ -1,7 +1,6 @@ import lodashGet from 'lodash/get'; import React, {Component} from 'react'; import {View} from 'react-native'; -import PropTypes from 'prop-types'; import styles from '../../../../styles/styles'; import SidebarLinks from '../SidebarLinks'; import ScreenWrapper from '../../../../components/ScreenWrapper'; @@ -14,18 +13,13 @@ import * as Welcome from '../../../../libs/actions/Welcome'; import withDrawerState from '../../../../components/withDrawerState'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions'; import compose from '../../../../libs/compose'; +import sidebarPropTypes from './sidebarPropTypes'; const propTypes = { - /** reportID in the current navigation state */ - reportIDFromRoute: PropTypes.string, - + ...sidebarPropTypes, ...windowDimensionsPropTypes, }; -const defaultProps = { - reportIDFromRoute: '', -}; - class BaseSidebarScreen extends Component { constructor(props) { super(props); @@ -83,7 +77,6 @@ class BaseSidebarScreen extends Component { } BaseSidebarScreen.propTypes = propTypes; -BaseSidebarScreen.defaultProps = defaultProps; export default compose( withWindowDimensions, diff --git a/src/pages/home/sidebar/SidebarScreen/PopoverModal.js b/src/pages/home/sidebar/SidebarScreen/PopoverModal.js index 19e90de9bdcb..54f99ccbd9f6 100644 --- a/src/pages/home/sidebar/SidebarScreen/PopoverModal.js +++ b/src/pages/home/sidebar/SidebarScreen/PopoverModal.js @@ -12,26 +12,33 @@ import PopoverMenu from '../../../../components/PopoverMenu'; import CONST from '../../../../CONST'; import FAB from '../../../../components/FAB'; import compose from '../../../../libs/compose'; -import withLocalize from '../../../../components/withLocalize'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; import withWindowDimensions from '../../../../components/withWindowDimensions'; import ONYXKEYS from '../../../../ONYXKEYS'; -import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes'; -const popoverModalBasePropTypes = { +const propTypes = { /* Callback function when the menu is shown */ onShowCreateMenu: PropTypes.func, /* Callback function before the menu is hidden */ onHideCreateMenu: PropTypes.func, -}; -const propTypes = { - ...popoverModalBasePropTypes, - ...sidebarPropTypes, + + /** The list of policies the user has access to. */ + allPolicies: PropTypes.shape({ + /** The policy name */ + name: PropTypes.string, + }), + + /* Beta features list */ + betas: PropTypes.arrayOf(PropTypes.string), + + ...withLocalizePropTypes, }; const defaultProps = { onHideCreateMenu: () => {}, onShowCreateMenu: () => {}, - ...sidebarDefaultProps, + allPolicies: {}, + betas: [], }; /** @@ -149,7 +156,6 @@ class PopoverModal extends React.Component { PopoverModal.propTypes = propTypes; PopoverModal.defaultProps = defaultProps; -export {popoverModalBasePropTypes}; export default compose( withLocalize, withWindowDimensions, diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.js index 46e4c9703133..75b5e07e7150 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes'; +import sidebarPropTypes from './sidebarPropTypes'; import BaseSidebarScreen from './BaseSidebarScreen'; import PopoverModal from './PopoverModal'; @@ -36,7 +36,6 @@ const SidebarScreen = (props) => { }; SidebarScreen.propTypes = sidebarPropTypes; -SidebarScreen.defaultProps = sidebarDefaultProps; SidebarScreen.displayName = 'SidebarScreen'; export default SidebarScreen; diff --git a/src/pages/home/sidebar/SidebarScreen/index.native.js b/src/pages/home/sidebar/SidebarScreen/index.native.js index 37bde3cd2ef1..cbf0d5c085a0 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.native.js +++ b/src/pages/home/sidebar/SidebarScreen/index.native.js @@ -1,5 +1,5 @@ import React from 'react'; -import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes'; +import sidebarPropTypes from './sidebarPropTypes'; import BaseSidebarScreen from './BaseSidebarScreen'; import PopoverModal from './PopoverModal'; @@ -14,7 +14,6 @@ const SidebarScreen = props => ( ); SidebarScreen.propTypes = sidebarPropTypes; -SidebarScreen.defaultProps = sidebarDefaultProps; SidebarScreen.displayName = 'SidebarScreen'; export default SidebarScreen; diff --git a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js b/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js index 996bba9d676b..3affaa2d00be 100644 --- a/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js +++ b/src/pages/home/sidebar/SidebarScreen/sidebarPropTypes.js @@ -1,26 +1,8 @@ import PropTypes from 'prop-types'; -import {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions'; -import {withLocalizePropTypes} from '../../../../components/withLocalize'; const sidebarPropTypes = { - /** The list of policies the user has access to. */ - allPolicies: PropTypes.shape({ - /** The policy name */ - name: PropTypes.string, - }), - - /* Beta features list */ - betas: PropTypes.arrayOf(PropTypes.string), - - ...windowDimensionsPropTypes, - - ...withLocalizePropTypes, + /** reportID in the current navigation state */ + reportIDFromRoute: PropTypes.string, }; - -const sidebarDefaultProps = { - allPolicies: {}, - betas: [], -}; - -export {sidebarPropTypes, sidebarDefaultProps}; +export default sidebarPropTypes; From ba3b9bfae3d6940436d71c7f97d434872c4d14ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 28 Oct 2022 15:39:17 +0200 Subject: [PATCH 07/12] removed onyx subscribe, use shouldComponentUpdate --- .../ScreenWrapper/BaseScreenWrapper.js | 38 ++++++++++++------- src/libs/onyxSubscribe.js | 12 ------ .../SidebarScreen/BaseSidebarScreen.js | 22 +++++------ 3 files changed, 34 insertions(+), 38 deletions(-) delete mode 100644 src/libs/onyxSubscribe.js diff --git a/src/components/ScreenWrapper/BaseScreenWrapper.js b/src/components/ScreenWrapper/BaseScreenWrapper.js index 732c98418457..a4282cea40e9 100644 --- a/src/components/ScreenWrapper/BaseScreenWrapper.js +++ b/src/components/ScreenWrapper/BaseScreenWrapper.js @@ -2,6 +2,7 @@ import {KeyboardAvoidingView, View} from 'react-native'; import React from 'react'; import {SafeAreaInsetsContext} from 'react-native-safe-area-context'; import _ from 'underscore'; +import {withOnyx} from 'react-native-onyx'; import CONST from '../../CONST'; import KeyboardShortcut from '../../libs/KeyboardShortcut'; import Navigation from '../../libs/Navigation/Navigation'; @@ -17,7 +18,6 @@ import withWindowDimensions from '../withWindowDimensions'; import ONYXKEYS from '../../ONYXKEYS'; import {withNetwork} from '../OnyxProvider'; import {propTypes, defaultProps} from './propTypes'; -import onyxSubscribe from '../../libs/onyxSubscribe'; class BaseScreenWrapper extends React.Component { constructor(props) { @@ -29,18 +29,9 @@ class BaseScreenWrapper extends React.Component { } componentDidMount() { - let willAlertModalBecomeVisible = false; - - this.unsubscribeOnyx = onyxSubscribe({ - key: ONYXKEYS.MODAL, - callback: (object) => { - willAlertModalBecomeVisible = object.willAlertModalBecomeVisible; - }, - }); - const shortcutConfig = CONST.KEYBOARD_SHORTCUTS.ESCAPE; this.unsubscribeEscapeKey = KeyboardShortcut.subscribe(shortcutConfig.shortcutKey, () => { - if (willAlertModalBecomeVisible) { + if (this.props.modal.willAlertModalBecomeVisible) { return; } @@ -53,6 +44,23 @@ class BaseScreenWrapper extends React.Component { }); } + /** + * We explicitly want to ignore if props.modal changes, and only want to rerender if + * any of the other props **used for the rendering output** is changed. + * @param {Object} nextProps + * @param {Object} nextState + * @returns {boolean} + */ + shouldComponentUpdate(nextProps, nextState) { + return this.state !== nextState + || this.props.children !== nextProps.children + || this.props.network.isOffline !== nextProps.network.isOffline + || this.props.includePaddingBottom !== nextProps.includePaddingBottom + || this.props.includePaddingTop !== nextProps.includePaddingTop + || this.props.isSmallScreenWidth !== nextProps.isSmallScreenWidth + || this.props.keyboardAvoidingViewBehavior !== nextProps.keyboardAvoidingViewBehavior; + } + componentWillUnmount() { if (this.unsubscribeEscapeKey) { this.unsubscribeEscapeKey(); @@ -60,9 +68,6 @@ class BaseScreenWrapper extends React.Component { if (this.unsubscribeTransitionEnd) { this.unsubscribeTransitionEnd(); } - if (this.unsubscribeOnyx) { - this.unsubscribeOnyx(); - } } render() { @@ -118,5 +123,10 @@ BaseScreenWrapper.defaultProps = defaultProps; export default compose( withNavigation, withWindowDimensions, + withOnyx({ + modal: { + key: ONYXKEYS.MODAL, + }, + }), withNetwork(), )(BaseScreenWrapper); diff --git a/src/libs/onyxSubscribe.js b/src/libs/onyxSubscribe.js deleted file mode 100644 index 600d010ed27f..000000000000 --- a/src/libs/onyxSubscribe.js +++ /dev/null @@ -1,12 +0,0 @@ -import Onyx from 'react-native-onyx'; - -/** - * Connect to onyx data. Same params as Onyx.connect(), but returns a function to unsubscribe. - * - * @param {Object} mapping Same as for Onyx.connect() - * @return {function(): void} Unsubscribe callback - */ -export default (mapping) => { - const connectionId = Onyx.connect(mapping); - return () => Onyx.disconnect(connectionId); -}; diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js index 40f8dab3d396..2f9eb5c4c2bc 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js @@ -58,18 +58,16 @@ class BaseSidebarScreen extends Component { style={[styles.sidebar]} > {({insets}) => ( - <> - - - - + + + )} ); From b0c73f145090bd509872688eb53829a88edb9b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 28 Oct 2022 15:47:02 +0200 Subject: [PATCH 08/12] code style --- src/components/ScreenWrapper/BaseScreenWrapper.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ScreenWrapper/BaseScreenWrapper.js b/src/components/ScreenWrapper/BaseScreenWrapper.js index a4282cea40e9..56a0c30b2ecf 100644 --- a/src/components/ScreenWrapper/BaseScreenWrapper.js +++ b/src/components/ScreenWrapper/BaseScreenWrapper.js @@ -54,11 +54,11 @@ class BaseScreenWrapper extends React.Component { shouldComponentUpdate(nextProps, nextState) { return this.state !== nextState || this.props.children !== nextProps.children - || this.props.network.isOffline !== nextProps.network.isOffline - || this.props.includePaddingBottom !== nextProps.includePaddingBottom - || this.props.includePaddingTop !== nextProps.includePaddingTop - || this.props.isSmallScreenWidth !== nextProps.isSmallScreenWidth - || this.props.keyboardAvoidingViewBehavior !== nextProps.keyboardAvoidingViewBehavior; + || this.props.network.isOffline !== nextProps.network.isOffline + || this.props.includePaddingBottom !== nextProps.includePaddingBottom + || this.props.includePaddingTop !== nextProps.includePaddingTop + || this.props.isSmallScreenWidth !== nextProps.isSmallScreenWidth + || this.props.keyboardAvoidingViewBehavior !== nextProps.keyboardAvoidingViewBehavior; } componentWillUnmount() { From 83efc2b5ff3587f3e76501759196bc2654a4de88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 28 Oct 2022 16:00:42 +0200 Subject: [PATCH 09/12] Apply suggestions from code review Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/pages/home/sidebar/SidebarScreen/PopoverModal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/PopoverModal.js b/src/pages/home/sidebar/SidebarScreen/PopoverModal.js index 54f99ccbd9f6..d9c2ea3a001d 100644 --- a/src/pages/home/sidebar/SidebarScreen/PopoverModal.js +++ b/src/pages/home/sidebar/SidebarScreen/PopoverModal.js @@ -69,8 +69,8 @@ class PopoverModal extends React.Component { /** * Method called either when: - * Pressing the floating action button to open the CreateMenu modal - * Selecting an item on CreateMenu or closing it by clicking outside of the modal component + * - Pressing the floating action button to open the CreateMenu modal + * - Selecting an item on CreateMenu or closing it by clicking outside of the modal component */ hideCreateMenu() { this.props.onHideCreateMenu(); From a759d82a1d3b2a5369d16b5dc68933ef8a81d406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sat, 29 Oct 2022 13:08:22 +0200 Subject: [PATCH 10/12] fix issue after merge --- ios/NewExpensify.xcodeproj/project.pbxproj | 6 ++++-- src/pages/home/sidebar/SidebarScreen/PopoverModal.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index cad2b5f4eb2e..5b46ea16f0cf 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -750,7 +750,7 @@ COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -775,6 +775,7 @@ ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; }; name = Debug; @@ -811,7 +812,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -828,6 +829,7 @@ "\"$(inherited)\"", ); MTL_ENABLE_DEBUG_INFO = NO; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; diff --git a/src/pages/home/sidebar/SidebarScreen/PopoverModal.js b/src/pages/home/sidebar/SidebarScreen/PopoverModal.js index d9c2ea3a001d..6b62f5875dd2 100644 --- a/src/pages/home/sidebar/SidebarScreen/PopoverModal.js +++ b/src/pages/home/sidebar/SidebarScreen/PopoverModal.js @@ -10,7 +10,7 @@ import Permissions from '../../../../libs/Permissions'; import * as Policy from '../../../../libs/actions/Policy'; import PopoverMenu from '../../../../components/PopoverMenu'; import CONST from '../../../../CONST'; -import FAB from '../../../../components/FAB'; +import FloatingActionButton from '../../../../components/FloatingActionButton'; import compose from '../../../../libs/compose'; import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; import withWindowDimensions from '../../../../components/withWindowDimensions'; @@ -142,7 +142,7 @@ class PopoverModal extends React.Component { ] : []), ]} /> - Date: Mon, 31 Oct 2022 14:12:14 +0100 Subject: [PATCH 11/12] revert change --- ios/NewExpensify.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 5b46ea16f0cf..0974290334e5 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -750,7 +750,7 @@ COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; From 74820f9cfda1b9ef190c3030e83ec7c15367ce9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 31 Oct 2022 14:13:59 +0100 Subject: [PATCH 12/12] revert change --- ios/NewExpensify.xcodeproj/project.pbxproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 0974290334e5..cad2b5f4eb2e 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -775,7 +775,6 @@ ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; }; name = Debug; @@ -812,7 +811,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -829,7 +828,6 @@ "\"$(inherited)\"", ); MTL_ENABLE_DEBUG_INFO = NO; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; };