Skip to content

Fix: Popover reaction list does not update dynamically #24024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}
/>
)),
);