Skip to content

Commit

Permalink
Merge pull request #24024 from tienifr/fix/23752-popover-reaction-lis…
Browse files Browse the repository at this point in the history
…t-not-update-dynamically

Fix: Popover reaction list does not update dynamically
  • Loading branch information
stitesExpensify authored Aug 8, 2023
2 parents 7a6486e + a7295d5 commit dec9f16
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 67 deletions.
2 changes: 1 addition & 1 deletion src/components/Reactions/ReportActionItemEmojiReactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function ReportActionItemEmojiReactions(props) {
};

const onReactionListOpen = (event) => {
reactionListRef.current.showReactionList(event, popoverReactionListAnchor.current, reaction);
reactionListRef.current.showReactionList(event, popoverReactionListAnchor.current, reactionEmojiName, props.reportActionID);
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,35 @@ import React from 'react';
import {Dimensions} from 'react-native';
import lodashGet from 'lodash/get';
import _ from 'underscore';
import * as Report from '../../../../libs/actions/Report';
import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
import PopoverWithMeasuredContent from '../../../../components/PopoverWithMeasuredContent';
import BaseReactionList from './BaseReactionList';
import compose from '../../../../libs/compose';
import withCurrentUserPersonalDetails from '../../../../components/withCurrentUserPersonalDetails';
import * as PersonalDetailsUtils from '../../../../libs/PersonalDetailsUtils';
import * as EmojiUtils from '../../../../libs/EmojiUtils';
import CONST from '../../../../CONST';

class PopoverReactionList extends React.Component {
import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import * as Report from '../../../../../libs/actions/Report';
import withLocalize, {withLocalizePropTypes} from '../../../../../components/withLocalize';
import PopoverWithMeasuredContent from '../../../../../components/PopoverWithMeasuredContent';
import BaseReactionList from '../BaseReactionList';
import compose from '../../../../../libs/compose';
import withCurrentUserPersonalDetails from '../../../../../components/withCurrentUserPersonalDetails';
import * as PersonalDetailsUtils from '../../../../../libs/PersonalDetailsUtils';
import * as EmojiUtils from '../../../../../libs/EmojiUtils';
import CONST from '../../../../../CONST';
import ONYXKEYS from '../../../../../ONYXKEYS';
import EmojiReactionsPropTypes from '../../../../../components/Reactions/EmojiReactionsPropTypes';

const propTypes = {
reportActionID: PropTypes.string,
emojiName: PropTypes.string,
emojiReactions: EmojiReactionsPropTypes,

...withLocalizePropTypes,
};

const defaultProps = {
reportActionID: '',
emojiName: '',
emojiReactions: {},
};

class BasePopoverReactionList extends React.Component {
constructor(props) {
super(props);

Expand All @@ -28,40 +46,54 @@ class PopoverReactionList extends React.Component {
horizontal: 0,
vertical: 0,
},
users: [],
emojiCodes: [],
emojiName: '',
emojiCount: 0,
hasUserReacted: false,
};

this.onPopoverHideActionCallback = () => {};
this.reactionListAnchor = React.createRef();
this.showReactionList = this.showReactionList.bind(this);
this.hideReactionList = this.hideReactionList.bind(this);
this.measureReactionListPosition = this.measureReactionListPosition.bind(this);
this.getReactionListMeasuredLocation = this.getReactionListMeasuredLocation.bind(this);
this.getReactionInformation = this.getReactionInformation.bind(this);
this.dimensionsEventListener = null;
this.contentRef = React.createRef();
}

componentDidMount() {
this.dimensionsEventListener = Dimensions.addEventListener('change', this.measureReactionListPosition);
}

shouldComponentUpdate(nextProps, nextState) {
if (!this.state.isPopoverVisible && !nextState.isPopoverVisible) {
return false;
}

const previousLocale = lodashGet(this.props, 'preferredLocale', CONST.LOCALES.DEFAULT);
const nextLocale = lodashGet(nextProps, 'preferredLocale', CONST.LOCALES.DEFAULT);
const prevReaction = lodashGet(this.props.emojiReactions, this.props.emojiName);
const nextReaction = lodashGet(nextProps.emojiReactions, nextProps.emojiName);

return (
this.state.isPopoverVisible !== nextState.isPopoverVisible ||
this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition ||
previousLocale !== nextLocale ||
(this.state.isPopoverVisible && (this.state.reportActionID !== nextState.reportActionID || this.state.emojiName !== nextState.emojiName))
this.props.reportActionID !== nextProps.reportActionID ||
this.props.emojiName !== nextProps.emojiName ||
!_.isEqual(prevReaction, nextReaction) ||
!_.isEqual(this.state, nextState) ||
previousLocale !== nextLocale
);
}

componentDidUpdate() {
if (!this.state.isPopoverVisible) {
return;
}

// Hide the list when all reactions are removed
const isEmptyList = !_.some(lodashGet(this.props.emojiReactions, [this.props.emojiName, 'users']));
if (!isEmptyList) {
return;
}

this.hideReactionList();
}

componentWillUnmount() {
if (!this.dimensionsEventListener) {
return;
Expand All @@ -70,7 +102,7 @@ class PopoverReactionList extends React.Component {
}

/**
* Get the PopoverReactionList anchor position
* Get the BasePopoverReactionList anchor position
* We calculate the achor coordinates from measureInWindow async method
*
* @returns {Promise<Object>}
Expand All @@ -94,20 +126,23 @@ class PopoverReactionList extends React.Component {
getReactionInformation(selectedReaction) {
if (!selectedReaction) {
return {
users: [],
emojiCodes: [],
emojiName: '',
emojiCount: 0,
emojiCodes: [],
hasUserReacted: false,
users: [],
};
}
const reactionUsers = _.pick(selectedReaction.users, _.identity);
const emojiCount = _.map(reactionUsers, (user) => user).length;
const userAccountIDs = _.map(reactionUsers, (user, accountID) => Number(accountID));
const emoji = EmojiUtils.findEmojiByName(selectedReaction.emojiName);
const emojiName = selectedReaction.emojiName;
const emoji = EmojiUtils.findEmojiByName(emojiName);
const emojiCodes = EmojiUtils.getUniqueEmojiCodes(emoji, selectedReaction.users);
const hasUserReacted = Report.hasAccountIDEmojiReacted(this.props.currentUserPersonalDetails.accountID, reactionUsers);
const users = PersonalDetailsUtils.getPersonalDetailsByIDs(userAccountIDs, this.props.currentUserPersonalDetails.accountID, true);
return {
emojiName,
emojiCount,
emojiCodes,
hasUserReacted,
Expand All @@ -120,15 +155,10 @@ class PopoverReactionList extends React.Component {
*
* @param {Object} [event] - A press event.
* @param {Element} reactionListAnchor - reactionListAnchor
* @param {Object} emojiReaction
* @param {String} emojiName - Name of emoji
*/
showReactionList(event, reactionListAnchor, emojiReaction) {
showReactionList(event, reactionListAnchor) {
const nativeEvent = event.nativeEvent || {};
this.reactionListAnchor = reactionListAnchor;
const selectedReaction = emojiReaction;
const {emojiName} = emojiReaction;
const {emojiCount, emojiCodes, hasUserReacted, users} = this.getReactionInformation(selectedReaction);
this.reactionListAnchor.current = reactionListAnchor;
this.getReactionListMeasuredLocation().then(({x, y}) => {
this.setState({
cursorRelativePosition: {
Expand All @@ -139,18 +169,13 @@ class PopoverReactionList extends React.Component {
horizontal: nativeEvent.pageX,
vertical: nativeEvent.pageY,
},
users,
emojiName,
emojiCodes,
emojiCount,
isPopoverVisible: true,
hasUserReacted,
});
});
}

/**
* This gets called on Dimensions change to find the anchor coordinates for the action PopoverReactionList.
* This gets called on Dimensions change to find the anchor coordinates for the action BasePopoverReactionList.
*/
measureReactionListPosition() {
if (!this.state.isPopoverVisible) {
Expand Down Expand Up @@ -179,36 +204,46 @@ class PopoverReactionList extends React.Component {
}

render() {
const selectedReaction = this.state.isPopoverVisible ? lodashGet(this.props.emojiReactions, [this.props.emojiName]) : null;
const {emojiName, emojiCount, emojiCodes, hasUserReacted, users} = this.getReactionInformation(selectedReaction);

return (
<>
<PopoverWithMeasuredContent
isVisible={this.state.isPopoverVisible}
<PopoverWithMeasuredContent
isVisible={this.state.isPopoverVisible}
onClose={this.hideReactionList}
anchorPosition={this.state.popoverAnchorPosition}
animationIn="fadeIn"
disableAnimation={false}
animationOutTiming={1}
shouldSetModalVisibility={false}
fullscreen
withoutOverlay
anchorRef={this.reactionListAnchor}
>
<BaseReactionList
type={this.state.type}
isVisible
users={users}
emojiName={emojiName}
emojiCodes={emojiCodes}
emojiCount={emojiCount}
onClose={this.hideReactionList}
anchorPosition={this.state.popoverAnchorPosition}
animationIn="fadeIn"
disableAnimation={false}
animationOutTiming={1}
shouldSetModalVisibility={false}
fullscreen
withoutOverlay
anchorRef={this.reactionListAnchor}
>
<BaseReactionList
type={this.state.type}
isVisible
users={this.state.users}
emojiName={this.state.emojiName}
emojiCodes={this.state.emojiCodes}
emojiCount={this.state.emojiCount}
onClose={this.hideReactionList}
hasUserReacted={this.state.hasUserReacted}
/>
</PopoverWithMeasuredContent>
</>
hasUserReacted={hasUserReacted}
/>
</PopoverWithMeasuredContent>
);
}
}

PopoverReactionList.propTypes = withLocalizePropTypes;

export default compose(withLocalize, withCurrentUserPersonalDetails)(PopoverReactionList);
BasePopoverReactionList.propTypes = propTypes;
BasePopoverReactionList.defaultProps = defaultProps;

export default compose(
withLocalize,
withCurrentUserPersonalDetails,
withOnyx({
emojiReactions: {
key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`,
},
}),
)(BasePopoverReactionList);
55 changes: 55 additions & 0 deletions src/pages/home/report/ReactionList/PopoverReactionList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react';
import PropTypes from 'prop-types';
import BasePopoverReactionList from './BasePopoverReactionList';

const propTypes = {
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
};

const defaultProps = {
innerRef: () => {},
};

function PopoverReactionList(props) {
const innerReactionListRef = useRef();
const [reactionListReportActionID, setReactionListReportActionID] = useState('');
const [reactionListEmojiName, setReactionListEmojiName] = useState('');

/**
* Show the ReactionList modal popover.
*
* @param {Object} [event] - A press event.
* @param {Element} reactionListAnchor - reactionListAnchor
* @param {String} emojiName - Name of emoji
* @param {String} reportActionID - ID of the report action
*/
const showReactionList = (event, reactionListAnchor, emojiName, reportActionID) => {
setReactionListReportActionID(reportActionID);
setReactionListEmojiName(emojiName);
innerReactionListRef.current.showReactionList(event, reactionListAnchor);
};

useImperativeHandle(props.innerRef, () => ({showReactionList}), []);

return (
<BasePopoverReactionList
ref={innerReactionListRef}
reportActionID={reactionListReportActionID}
emojiName={reactionListEmojiName}
/>
);
}

PopoverReactionList.propTypes = propTypes;
PopoverReactionList.defaultProps = defaultProps;
PopoverReactionList.displayName = 'PopoverReactionList';

export default React.memo(
forwardRef((props, ref) => (
<PopoverReactionList
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
innerRef={ref}
/>
)),
);

0 comments on commit dec9f16

Please sign in to comment.