diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 438e6cce010e..93ce9be9c415 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -1,11 +1,11 @@ import _ from 'underscore'; -import React, {Component} from 'react'; +import React, {useState, useMemo} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; -import lodashGet from 'lodash/get'; import Popover from './Popover'; import {propTypes as popoverPropTypes, defaultProps as defaultPopoverProps} from './Popover/popoverPropTypes'; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; +import useWindowDimensions from '../hooks/useWindowDimensions'; +import {windowDimensionsPropTypes} from './withWindowDimensions'; import CONST from '../CONST'; import styles from '../styles/styles'; import {computeHorizontalShift, computeVerticalShift} from '../styles/getPopoverWithMeasuredContentStyles'; @@ -13,7 +13,8 @@ import {computeHorizontalShift, computeVerticalShift} from '../styles/getPopover const propTypes = { // All popover props except: // 1) anchorPosition (which is overridden for this component) - ..._.omit(popoverPropTypes, ['anchorPosition']), + // 2) windowDimensionsPropTypes - we exclude them because we use useWindowDimensions hook instead + ..._.omit(popoverPropTypes, ['anchorPosition', ..._.keys(windowDimensionsPropTypes)]), /** The horizontal and vertical anchors points for the popover */ anchorPosition: PropTypes.shape({ @@ -35,8 +36,6 @@ const propTypes = { height: PropTypes.number, width: PropTypes.number, }), - - ...windowDimensionsPropTypes, }; const defaultProps = { @@ -59,49 +58,24 @@ const defaultProps = { * This way, we can shift the position of popover so that the content is anchored where we want it relative to the * anchor position. */ -class PopoverWithMeasuredContent extends Component { - constructor(props) { - super(props); - - this.popoverWidth = lodashGet(this.props, 'popoverDimensions.width', 0); - this.popoverHeight = lodashGet(this.props, 'popoverDimensions.height', 0); - - this.state = { - isContentMeasured: this.popoverWidth > 0 && this.popoverHeight > 0, - isVisible: false, - }; - this.measurePopover = this.measurePopover.bind(this); - } +function PopoverWithMeasuredContent(props) { + const {windowWidth, windowHeight} = useWindowDimensions(); + const [popoverWidth, setPopoverWidth] = useState(props.popoverDimensions.width); + const [popoverHeight, setPopoverHeight] = useState(props.popoverDimensions.height); + const [isContentMeasured, setIsContentMeasured] = useState(popoverWidth > 0 && popoverHeight > 0); + const [isVisible, setIsVisible] = useState(false); /** * When Popover becomes visible, we need to recalculate the Dimensions. * Skip render on Popover until recalculations have done by setting isContentMeasured false as early as possible. - * - * @static - * @param {Object} props - * @param {Object} state - * @return {Object|null} */ - static getDerivedStateFromProps(props, state) { + if (!isVisible && props.isVisible) { // When Popover is shown recalculate - if (!state.isVisible && props.isVisible) { - return {isContentMeasured: lodashGet(props, 'popoverDimensions.width', 0) > 0 && lodashGet(props, 'popoverDimensions.height', 0) > 0, isVisible: true}; - } - if (!props.isVisible) { - return {isVisible: false}; - } - return null; - } - - shouldComponentUpdate(nextProps, nextState) { - if (this.props.isVisible && (nextProps.windowWidth !== this.props.windowWidth || nextProps.windowHeight !== this.props.windowHeight)) { - return true; - } - - // This component does not require re-render until any prop or state changes as we get the necessary info - // at first render. This component is attached to each message on the Chat list thus we prevent its re-renders - return !_.isEqual(_.omit(this.props, ['windowWidth', 'windowHeight']), _.omit(nextProps, ['windowWidth', 'windowHeight'])) || !_.isEqual(this.state, nextState); + setIsContentMeasured(props.popoverDimensions.width > 0 && props.popoverDimensions.height > 0); + setIsVisible(true); + } else if (isVisible && !props.isVisible) { + setIsVisible(false); } /** @@ -109,88 +83,86 @@ class PopoverWithMeasuredContent extends Component { * * @param {Object} nativeEvent */ - measurePopover({nativeEvent}) { - this.popoverWidth = nativeEvent.layout.width; - this.popoverHeight = nativeEvent.layout.height; - this.setState({isContentMeasured: true}); - } + const measurePopover = ({nativeEvent}) => { + setPopoverWidth(nativeEvent.layout.width); + setPopoverHeight(nativeEvent.layout.height); + setIsContentMeasured(true); + }; - /** - * Calculate the adjusted position of the popover. - * - * @returns {Object} - */ - calculateAdjustedAnchorPosition() { + const adjustedAnchorPosition = useMemo(() => { let horizontalConstraint; - switch (this.props.anchorAlignment.horizontal) { + switch (props.anchorAlignment.horizontal) { case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT: - horizontalConstraint = {left: this.props.anchorPosition.horizontal - this.popoverWidth}; + horizontalConstraint = {left: props.anchorPosition.horizontal - popoverWidth}; break; case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER: horizontalConstraint = { - left: Math.floor(this.props.anchorPosition.horizontal - this.popoverWidth / 2), + left: Math.floor(props.anchorPosition.horizontal - popoverWidth / 2), }; break; case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT: default: - horizontalConstraint = {left: this.props.anchorPosition.horizontal}; + horizontalConstraint = {left: props.anchorPosition.horizontal}; } let verticalConstraint; - switch (this.props.anchorAlignment.vertical) { + switch (props.anchorAlignment.vertical) { case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM: - verticalConstraint = {top: this.props.anchorPosition.vertical - this.popoverHeight}; + verticalConstraint = {top: props.anchorPosition.vertical - popoverHeight}; break; case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.CENTER: verticalConstraint = { - top: Math.floor(this.props.anchorPosition.vertical - this.popoverHeight / 2), + top: Math.floor(props.anchorPosition.vertical - popoverHeight / 2), }; break; case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP: default: - verticalConstraint = {top: this.props.anchorPosition.vertical}; + verticalConstraint = {top: props.anchorPosition.vertical}; } return { ...horizontalConstraint, ...verticalConstraint, }; - } - - render() { - const adjustedAnchorPosition = this.calculateAdjustedAnchorPosition(); - const horizontalShift = computeHorizontalShift(adjustedAnchorPosition.left, this.popoverWidth, this.props.windowWidth); - const verticalShift = computeVerticalShift(adjustedAnchorPosition.top, this.popoverHeight, this.props.windowHeight); - const shiftedAnchorPosition = { - left: adjustedAnchorPosition.left + horizontalShift, - top: adjustedAnchorPosition.top + verticalShift, - }; - return this.state.isContentMeasured ? ( - - {this.props.children} - - ) : ( - /* - This is an invisible view used to measure the size of the popover, - before it ever needs to be displayed. - We do this because we need to know its dimensions in order to correctly animate the popover, - but we can't measure its dimensions without first rendering it. - */ - - {this.props.children} - - ); - } + }, [props.anchorPosition, props.anchorAlignment, popoverWidth, popoverHeight]); + + const horizontalShift = computeHorizontalShift(adjustedAnchorPosition.left, popoverWidth, windowWidth); + const verticalShift = computeVerticalShift(adjustedAnchorPosition.top, popoverHeight, windowHeight); + const shiftedAnchorPosition = { + left: adjustedAnchorPosition.left + horizontalShift, + top: adjustedAnchorPosition.top + verticalShift, + }; + return isContentMeasured ? ( + + {props.children} + + ) : ( + /* + This is an invisible view used to measure the size of the popover, + before it ever needs to be displayed. + We do this because we need to know its dimensions in order to correctly animate the popover, + but we can't measure its dimensions without first rendering it. + */ + + {props.children} + + ); } PopoverWithMeasuredContent.propTypes = propTypes; PopoverWithMeasuredContent.defaultProps = defaultProps; +PopoverWithMeasuredContent.displayName = 'PopoverWithMeasuredContent'; -export default withWindowDimensions(PopoverWithMeasuredContent); +export default React.memo(PopoverWithMeasuredContent, (prevProps, nextProps) => { + if (prevProps.isVisible === nextProps.isVisible && nextProps.isVisible === false) { + return true; + } + return _.isEqual(prevProps, nextProps); +});