Skip to content

Commit

Permalink
Merge pull request Expensify#12369 from margelo/perf/memoize-sidebar-2
Browse files Browse the repository at this point in the history
perf: reduce rerenders of sidebar
  • Loading branch information
mountiny authored Nov 21, 2022
2 parents 03c8d8b + a140dc6 commit 3528ad5
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 248 deletions.
12 changes: 12 additions & 0 deletions src/components/ScreenWrapper/BaseScreenWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ 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 !_.isEqual(this.state, nextState)
|| !_.isEqual(_.omit(this.props, 'modal'), _.omit(nextProps, 'modal'));
}

componentWillUnmount() {
if (this.unsubscribeEscapeKey) {
this.unsubscribeEscapeKey();
Expand Down
190 changes: 11 additions & 179 deletions src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
Original file line number Diff line number Diff line change
@@ -1,145 +1,42 @@
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 FloatingActionButton from '../../../../components/FloatingActionButton';
import ScreenWrapper from '../../../../components/ScreenWrapper';
import compose from '../../../../libs/compose';
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 withNavigationFocus from '../../../../components/withNavigationFocus';
import KeyboardShortcutsModal from '../../../../components/KeyboardShortcutsModal';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions';
import compose from '../../../../libs/compose';
import sidebarPropTypes from './sidebarPropTypes';

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,
};
const defaultProps = {
onHideCreateMenu: () => {},
onShowCreateMenu: () => {},
...sidebarDefaultProps,
...windowDimensionsPropTypes,
};

class BaseSidebarScreen extends Component {
constructor(props) {
super(props);

this.hideCreateMenu = this.hideCreateMenu.bind(this);
this.startTimer = this.startTimer.bind(this);
this.showCreateMenu = this.showCreateMenu.bind(this);

this.state = {
isCreateMenuActive: false,
};
this.navigateToSettings = this.navigateToSettings.bind(this);
}

componentDidMount() {
Performance.markStart(CONST.TIMING.SIDEBAR_LOADED);
Timing.start(CONST.TIMING.SIDEBAR_LOADED, true);

const routes = lodashGet(this.props.navigation.getState(), 'routes', []);
Welcome.show({routes, showCreateMenu: this.showCreateMenu});
}

componentDidUpdate(prevProps) {
if (!this.didScreenBecomeInactive(prevProps)) {
return;
}

// Hide menu manually when other pages are opened using shortcut key
this.hideCreateMenu();
}

/**
* Check if LHN status changed from active to inactive.
* Used to close already opened FAB menu when open any other pages (i.e. Press Command + K on web).
*
* @param {Object} prevProps
* @return {Boolean}
* Method called when avatar is clicked
*/
didScreenBecomeInactive(prevProps) {
// When the Drawer gets closed and ReportScreen is shown
if (!this.props.isDrawerOpen && prevProps.isDrawerOpen) {
return true;
}

// When any other page is opened over LHN
if (!this.props.isFocused && prevProps.isFocused) {
return true;
}

return false;
}

/**
* Check if LHN is inactive.
* Used to prevent FAB menu showing after opening any other pages.
*
* @return {Boolean}
*/
isScreenInactive() {
// When drawer is closed and Report page is open
if (this.props.isSmallScreenWidth && !this.props.isDrawerOpen) {
return true;
}

// When any other page is open
if (!this.props.isFocused) {
return true;
}

return false;
}

/**
* Method called when we click the floating action button
*/
showCreateMenu() {
if (this.isScreenInactive()) {
// Prevent showing menu when click FAB icon quickly after opening other pages
return;
}
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() {
if (!this.state.isCreateMenuActive) {
return;
}
this.props.onHideCreateMenu();
this.setState({
isCreateMenuActive: false,
});
navigateToSettings() {
Navigation.navigate(ROUTES.SETTINGS);
}

/**
Expand All @@ -151,8 +48,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 (
<ScreenWrapper
includePaddingBottom={false}
Expand All @@ -164,76 +59,14 @@ class BaseSidebarScreen extends Component {
<SidebarLinks
onLinkClick={this.startTimer}
insets={insets}
onAvatarClick={this.navigateToSettings}
isSmallScreenWidth={this.props.isSmallScreenWidth}
isDrawerOpen={this.props.isDrawerOpen}
isCreateMenuOpen={this.state.isCreateMenuActive}
reportIDFromRoute={this.props.reportIDFromRoute}
/>
<FloatingActionButton
accessibilityLabel={this.props.translate('sidebarScreen.fabNewChat')}
accessibilityRole="button"
isActive={this.state.isCreateMenuActive}
onPress={this.showCreateMenu}
/>
</View>
<KeyboardShortcutsModal />
<PopoverMenu
onClose={this.hideCreateMenu}
isVisible={this.state.isCreateMenuActive}
anchorPosition={styles.createMenuPositionSidebar}
onItemSelected={this.hideCreateMenu}
fromSidebarMediumScreen={!this.props.isSmallScreenWidth}
menuItems={[
{
icon: Expensicons.ChatBubble,
text: this.props.translate('sidebarScreen.newChat'),
onSelected: () => 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.hasActiveFreePolicy(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(),
},
] : []),
]}
/>
{this.props.children}
</>
)}
</ScreenWrapper>
Expand All @@ -242,9 +75,8 @@ class BaseSidebarScreen extends Component {
}

BaseSidebarScreen.propTypes = propTypes;
BaseSidebarScreen.defaultProps = defaultProps;

export default compose(
withWindowDimensions,
withDrawerState,
withNavigationFocus,
)(BaseSidebarScreen);
Loading

0 comments on commit 3528ad5

Please sign in to comment.