1
1
import _ from 'underscore' ;
2
- import React , { Component } from 'react' ;
2
+ import React , { useState , useMemo } from 'react' ;
3
3
import PropTypes from 'prop-types' ;
4
4
import { View } from 'react-native' ;
5
- import lodashGet from 'lodash/get' ;
6
5
import Popover from './Popover' ;
7
6
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' ;
9
9
import CONST from '../CONST' ;
10
10
import styles from '../styles/styles' ;
11
11
import { computeHorizontalShift , computeVerticalShift } from '../styles/getPopoverWithMeasuredContentStyles' ;
12
12
13
13
const propTypes = {
14
14
// All popover props except:
15
15
// 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 ) ] ) ,
17
18
18
19
/** The horizontal and vertical anchors points for the popover */
19
20
anchorPosition : PropTypes . shape ( {
@@ -35,8 +36,6 @@ const propTypes = {
35
36
height : PropTypes . number ,
36
37
width : PropTypes . number ,
37
38
} ) ,
38
-
39
- ...windowDimensionsPropTypes ,
40
39
} ;
41
40
42
41
const defaultProps = {
@@ -59,138 +58,111 @@ const defaultProps = {
59
58
* This way, we can shift the position of popover so that the content is anchored where we want it relative to the
60
59
* anchor position.
61
60
*/
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
- } ;
73
61
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 ) ;
76
68
77
69
/**
78
70
* When Popover becomes visible, we need to recalculate the Dimensions.
79
71
* 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 }
85
72
*/
86
- static getDerivedStateFromProps ( props , state ) {
73
+ if ( ! isVisible && props . isVisible ) {
87
74
// 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 ) ;
105
79
}
106
80
107
81
/**
108
82
* Measure the size of the popover's content.
109
83
*
110
84
* @param {Object } nativeEvent
111
85
*/
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
+ } ;
117
91
118
- /**
119
- * Calculate the adjusted position of the popover.
120
- *
121
- * @returns {Object }
122
- */
123
- calculateAdjustedAnchorPosition ( ) {
92
+ const adjustedAnchorPosition = useMemo ( ( ) => {
124
93
let horizontalConstraint ;
125
- switch ( this . props . anchorAlignment . horizontal ) {
94
+ switch ( props . anchorAlignment . horizontal ) {
126
95
case CONST . MODAL . ANCHOR_ORIGIN_HORIZONTAL . RIGHT :
127
- horizontalConstraint = { left : this . props . anchorPosition . horizontal - this . popoverWidth } ;
96
+ horizontalConstraint = { left : props . anchorPosition . horizontal - popoverWidth } ;
128
97
break ;
129
98
case CONST . MODAL . ANCHOR_ORIGIN_HORIZONTAL . CENTER :
130
99
horizontalConstraint = {
131
- left : Math . floor ( this . props . anchorPosition . horizontal - this . popoverWidth / 2 ) ,
100
+ left : Math . floor ( props . anchorPosition . horizontal - popoverWidth / 2 ) ,
132
101
} ;
133
102
break ;
134
103
case CONST . MODAL . ANCHOR_ORIGIN_HORIZONTAL . LEFT :
135
104
default :
136
- horizontalConstraint = { left : this . props . anchorPosition . horizontal } ;
105
+ horizontalConstraint = { left : props . anchorPosition . horizontal } ;
137
106
}
138
107
139
108
let verticalConstraint ;
140
- switch ( this . props . anchorAlignment . vertical ) {
109
+ switch ( props . anchorAlignment . vertical ) {
141
110
case CONST . MODAL . ANCHOR_ORIGIN_VERTICAL . BOTTOM :
142
- verticalConstraint = { top : this . props . anchorPosition . vertical - this . popoverHeight } ;
111
+ verticalConstraint = { top : props . anchorPosition . vertical - popoverHeight } ;
143
112
break ;
144
113
case CONST . MODAL . ANCHOR_ORIGIN_VERTICAL . CENTER :
145
114
verticalConstraint = {
146
- top : Math . floor ( this . props . anchorPosition . vertical - this . popoverHeight / 2 ) ,
115
+ top : Math . floor ( props . anchorPosition . vertical - popoverHeight / 2 ) ,
147
116
} ;
148
117
break ;
149
118
case CONST . MODAL . ANCHOR_ORIGIN_VERTICAL . TOP :
150
119
default :
151
- verticalConstraint = { top : this . props . anchorPosition . vertical } ;
120
+ verticalConstraint = { top : props . anchorPosition . vertical } ;
152
121
}
153
122
154
123
return {
155
124
...horizontalConstraint ,
156
125
...verticalConstraint ,
157
126
} ;
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
+ ) ;
191
157
}
192
158
193
159
PopoverWithMeasuredContent . propTypes = propTypes ;
194
160
PopoverWithMeasuredContent . defaultProps = defaultProps ;
161
+ PopoverWithMeasuredContent . displayName = 'PopoverWithMeasuredContent' ;
195
162
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