Skip to content

Commit 74ae95c

Browse files
authored
Merge pull request #22868 from alexxxwork/issue-16191
Component refactor: migrate PopoverWithMeasuredContent to function component
2 parents 8b23658 + c8b6e74 commit 74ae95c

File tree

1 file changed

+67
-95
lines changed

1 file changed

+67
-95
lines changed
Lines changed: 67 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import _ from 'underscore';
2-
import React, {Component} from 'react';
2+
import React, {useState, useMemo} from 'react';
33
import PropTypes from 'prop-types';
44
import {View} from 'react-native';
5-
import lodashGet from 'lodash/get';
65
import Popover from './Popover';
76
import {propTypes as popoverPropTypes, defaultProps as defaultPopoverProps} from './Popover/popoverPropTypes';
8-
import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
7+
import useWindowDimensions from '../hooks/useWindowDimensions';
8+
import {windowDimensionsPropTypes} from './withWindowDimensions';
99
import CONST from '../CONST';
1010
import styles from '../styles/styles';
1111
import {computeHorizontalShift, computeVerticalShift} from '../styles/getPopoverWithMeasuredContentStyles';
1212

1313
const propTypes = {
1414
// All popover props except:
1515
// 1) anchorPosition (which is overridden for this component)
16-
..._.omit(popoverPropTypes, ['anchorPosition']),
16+
// 2) windowDimensionsPropTypes - we exclude them because we use useWindowDimensions hook instead
17+
..._.omit(popoverPropTypes, ['anchorPosition', ..._.keys(windowDimensionsPropTypes)]),
1718

1819
/** The horizontal and vertical anchors points for the popover */
1920
anchorPosition: PropTypes.shape({
@@ -35,8 +36,6 @@ const propTypes = {
3536
height: PropTypes.number,
3637
width: PropTypes.number,
3738
}),
38-
39-
...windowDimensionsPropTypes,
4039
};
4140

4241
const defaultProps = {
@@ -59,138 +58,111 @@ const defaultProps = {
5958
* This way, we can shift the position of popover so that the content is anchored where we want it relative to the
6059
* anchor position.
6160
*/
62-
class PopoverWithMeasuredContent extends Component {
63-
constructor(props) {
64-
super(props);
65-
66-
this.popoverWidth = lodashGet(this.props, 'popoverDimensions.width', 0);
67-
this.popoverHeight = lodashGet(this.props, 'popoverDimensions.height', 0);
68-
69-
this.state = {
70-
isContentMeasured: this.popoverWidth > 0 && this.popoverHeight > 0,
71-
isVisible: false,
72-
};
7361

74-
this.measurePopover = this.measurePopover.bind(this);
75-
}
62+
function PopoverWithMeasuredContent(props) {
63+
const {windowWidth, windowHeight} = useWindowDimensions();
64+
const [popoverWidth, setPopoverWidth] = useState(props.popoverDimensions.width);
65+
const [popoverHeight, setPopoverHeight] = useState(props.popoverDimensions.height);
66+
const [isContentMeasured, setIsContentMeasured] = useState(popoverWidth > 0 && popoverHeight > 0);
67+
const [isVisible, setIsVisible] = useState(false);
7668

7769
/**
7870
* When Popover becomes visible, we need to recalculate the Dimensions.
7971
* Skip render on Popover until recalculations have done by setting isContentMeasured false as early as possible.
80-
*
81-
* @static
82-
* @param {Object} props
83-
* @param {Object} state
84-
* @return {Object|null}
8572
*/
86-
static getDerivedStateFromProps(props, state) {
73+
if (!isVisible && props.isVisible) {
8774
// When Popover is shown recalculate
88-
if (!state.isVisible && props.isVisible) {
89-
return {isContentMeasured: lodashGet(props, 'popoverDimensions.width', 0) > 0 && lodashGet(props, 'popoverDimensions.height', 0) > 0, isVisible: true};
90-
}
91-
if (!props.isVisible) {
92-
return {isVisible: false};
93-
}
94-
return null;
95-
}
96-
97-
shouldComponentUpdate(nextProps, nextState) {
98-
if (this.props.isVisible && (nextProps.windowWidth !== this.props.windowWidth || nextProps.windowHeight !== this.props.windowHeight)) {
99-
return true;
100-
}
101-
102-
// This component does not require re-render until any prop or state changes as we get the necessary info
103-
// at first render. This component is attached to each message on the Chat list thus we prevent its re-renders
104-
return !_.isEqual(_.omit(this.props, ['windowWidth', 'windowHeight']), _.omit(nextProps, ['windowWidth', 'windowHeight'])) || !_.isEqual(this.state, nextState);
75+
setIsContentMeasured(props.popoverDimensions.width > 0 && props.popoverDimensions.height > 0);
76+
setIsVisible(true);
77+
} else if (isVisible && !props.isVisible) {
78+
setIsVisible(false);
10579
}
10680

10781
/**
10882
* Measure the size of the popover's content.
10983
*
11084
* @param {Object} nativeEvent
11185
*/
112-
measurePopover({nativeEvent}) {
113-
this.popoverWidth = nativeEvent.layout.width;
114-
this.popoverHeight = nativeEvent.layout.height;
115-
this.setState({isContentMeasured: true});
116-
}
86+
const measurePopover = ({nativeEvent}) => {
87+
setPopoverWidth(nativeEvent.layout.width);
88+
setPopoverHeight(nativeEvent.layout.height);
89+
setIsContentMeasured(true);
90+
};
11791

118-
/**
119-
* Calculate the adjusted position of the popover.
120-
*
121-
* @returns {Object}
122-
*/
123-
calculateAdjustedAnchorPosition() {
92+
const adjustedAnchorPosition = useMemo(() => {
12493
let horizontalConstraint;
125-
switch (this.props.anchorAlignment.horizontal) {
94+
switch (props.anchorAlignment.horizontal) {
12695
case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT:
127-
horizontalConstraint = {left: this.props.anchorPosition.horizontal - this.popoverWidth};
96+
horizontalConstraint = {left: props.anchorPosition.horizontal - popoverWidth};
12897
break;
12998
case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER:
13099
horizontalConstraint = {
131-
left: Math.floor(this.props.anchorPosition.horizontal - this.popoverWidth / 2),
100+
left: Math.floor(props.anchorPosition.horizontal - popoverWidth / 2),
132101
};
133102
break;
134103
case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT:
135104
default:
136-
horizontalConstraint = {left: this.props.anchorPosition.horizontal};
105+
horizontalConstraint = {left: props.anchorPosition.horizontal};
137106
}
138107

139108
let verticalConstraint;
140-
switch (this.props.anchorAlignment.vertical) {
109+
switch (props.anchorAlignment.vertical) {
141110
case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM:
142-
verticalConstraint = {top: this.props.anchorPosition.vertical - this.popoverHeight};
111+
verticalConstraint = {top: props.anchorPosition.vertical - popoverHeight};
143112
break;
144113
case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.CENTER:
145114
verticalConstraint = {
146-
top: Math.floor(this.props.anchorPosition.vertical - this.popoverHeight / 2),
115+
top: Math.floor(props.anchorPosition.vertical - popoverHeight / 2),
147116
};
148117
break;
149118
case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP:
150119
default:
151-
verticalConstraint = {top: this.props.anchorPosition.vertical};
120+
verticalConstraint = {top: props.anchorPosition.vertical};
152121
}
153122

154123
return {
155124
...horizontalConstraint,
156125
...verticalConstraint,
157126
};
158-
}
159-
160-
render() {
161-
const adjustedAnchorPosition = this.calculateAdjustedAnchorPosition();
162-
const horizontalShift = computeHorizontalShift(adjustedAnchorPosition.left, this.popoverWidth, this.props.windowWidth);
163-
const verticalShift = computeVerticalShift(adjustedAnchorPosition.top, this.popoverHeight, this.props.windowHeight);
164-
const shiftedAnchorPosition = {
165-
left: adjustedAnchorPosition.left + horizontalShift,
166-
top: adjustedAnchorPosition.top + verticalShift,
167-
};
168-
return this.state.isContentMeasured ? (
169-
<Popover
170-
// eslint-disable-next-line react/jsx-props-no-spreading
171-
{...this.props}
172-
anchorPosition={shiftedAnchorPosition}
173-
>
174-
{this.props.children}
175-
</Popover>
176-
) : (
177-
/*
178-
This is an invisible view used to measure the size of the popover,
179-
before it ever needs to be displayed.
180-
We do this because we need to know its dimensions in order to correctly animate the popover,
181-
but we can't measure its dimensions without first rendering it.
182-
*/
183-
<View
184-
style={styles.invisible}
185-
onLayout={this.measurePopover}
186-
>
187-
{this.props.children}
188-
</View>
189-
);
190-
}
127+
}, [props.anchorPosition, props.anchorAlignment, popoverWidth, popoverHeight]);
128+
129+
const horizontalShift = computeHorizontalShift(adjustedAnchorPosition.left, popoverWidth, windowWidth);
130+
const verticalShift = computeVerticalShift(adjustedAnchorPosition.top, popoverHeight, windowHeight);
131+
const shiftedAnchorPosition = {
132+
left: adjustedAnchorPosition.left + horizontalShift,
133+
top: adjustedAnchorPosition.top + verticalShift,
134+
};
135+
return isContentMeasured ? (
136+
<Popover
137+
// eslint-disable-next-line react/jsx-props-no-spreading
138+
{...props}
139+
anchorPosition={shiftedAnchorPosition}
140+
>
141+
{props.children}
142+
</Popover>
143+
) : (
144+
/*
145+
This is an invisible view used to measure the size of the popover,
146+
before it ever needs to be displayed.
147+
We do this because we need to know its dimensions in order to correctly animate the popover,
148+
but we can't measure its dimensions without first rendering it.
149+
*/
150+
<View
151+
style={styles.invisible}
152+
onLayout={measurePopover}
153+
>
154+
{props.children}
155+
</View>
156+
);
191157
}
192158

193159
PopoverWithMeasuredContent.propTypes = propTypes;
194160
PopoverWithMeasuredContent.defaultProps = defaultProps;
161+
PopoverWithMeasuredContent.displayName = 'PopoverWithMeasuredContent';
195162

196-
export default withWindowDimensions(PopoverWithMeasuredContent);
163+
export default React.memo(PopoverWithMeasuredContent, (prevProps, nextProps) => {
164+
if (prevProps.isVisible === nextProps.isVisible && nextProps.isVisible === false) {
165+
return true;
166+
}
167+
return _.isEqual(prevProps, nextProps);
168+
});

0 commit comments

Comments
 (0)