diff --git a/src/components/Reactions/ReportActionItemReactions.js b/src/components/Reactions/ReportActionItemReactions.js
index 8a818e1c7bff..d5da41c9f648 100644
--- a/src/components/Reactions/ReportActionItemReactions.js
+++ b/src/components/Reactions/ReportActionItemReactions.js
@@ -11,7 +11,7 @@ import * as Report from '../../libs/actions/Report';
import Tooltip from '../Tooltip';
import ReactionTooltipContent from './ReactionTooltipContent';
import * as EmojiUtils from '../../libs/EmojiUtils';
-import ReactionListRefContext from '../../pages/home/report/ReactionList/ReactionListRefContext';
+import ReportScreenContext from '../../pages/home/ReportScreenContext';
const propTypes = {
/**
@@ -47,7 +47,7 @@ const defaultProps = {
};
function ReportActionItemReactions(props) {
- const reactionListRef = useContext(ReactionListRefContext);
+ const {reactionListRef} = useContext(ReportScreenContext);
const popoverReactionListAnchor = useRef(null);
const reactionsWithCount = _.filter(props.reactions, (reaction) => reaction.users.length > 0);
diff --git a/src/hooks/useReportScrollManager/index.js b/src/hooks/useReportScrollManager/index.js
new file mode 100644
index 000000000000..13e7471f6eb1
--- /dev/null
+++ b/src/hooks/useReportScrollManager/index.js
@@ -0,0 +1,36 @@
+import {useContext} from 'react';
+import ReportScreenContext from '../../pages/home/ReportScreenContext';
+
+function useReportScrollManager() {
+ const {flatListRef} = useContext(ReportScreenContext);
+
+ /**
+ * Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because
+ * we are editing a comment.
+ *
+ * @param {Object} index
+ * @param {Boolean} isEditing
+ */
+ const scrollToIndex = (index, isEditing) => {
+ if (!flatListRef.current || isEditing) {
+ return;
+ }
+
+ flatListRef.current.scrollToIndex(index);
+ };
+
+ /**
+ * Scroll to the bottom of the flatlist.
+ */
+ const scrollToBottom = () => {
+ if (!flatListRef.current) {
+ return;
+ }
+
+ flatListRef.current.scrollToOffset({animated: false, offset: 0});
+ };
+
+ return {ref: flatListRef, scrollToIndex, scrollToBottom};
+}
+
+export default useReportScrollManager;
diff --git a/src/hooks/useReportScrollManager/index.native.js b/src/hooks/useReportScrollManager/index.native.js
new file mode 100644
index 000000000000..e206991efa03
--- /dev/null
+++ b/src/hooks/useReportScrollManager/index.native.js
@@ -0,0 +1,34 @@
+import {useContext} from 'react';
+import ReportScreenContext from '../../pages/home/ReportScreenContext';
+
+function useReportScrollManager() {
+ const {flatListRef} = useContext(ReportScreenContext);
+
+ /**
+ * Scroll to the provided index.
+ *
+ * @param {Object} index
+ */
+ const scrollToIndex = (index) => {
+ if (!flatListRef.current) {
+ return;
+ }
+
+ flatListRef.current.scrollToIndex(index);
+ };
+
+ /**
+ * Scroll to the bottom of the flatlist.
+ */
+ const scrollToBottom = () => {
+ if (!flatListRef.current) {
+ return;
+ }
+
+ flatListRef.current.scrollToOffset({animated: false, offset: 0});
+ };
+
+ return {ref: flatListRef, scrollToIndex, scrollToBottom};
+}
+
+export default useReportScrollManager;
diff --git a/src/libs/ReportScrollManager/index.js b/src/libs/ReportScrollManager/index.js
index f35abb74020a..1dd9922b33b8 100644
--- a/src/libs/ReportScrollManager/index.js
+++ b/src/libs/ReportScrollManager/index.js
@@ -1,34 +1,30 @@
-import React from 'react';
-
-// This ref is created using React.createRef here because this function is used by a component that doesn't have access
-// to the original ref.
-const flatListRef = React.createRef();
-
/**
* Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because
* we are editing a comment.
*
+ * @param {Object} ref
* @param {Object} index
* @param {Boolean} isEditing
*/
-function scrollToIndex(index, isEditing) {
- if (isEditing) {
+function scrollToIndex(ref, index, isEditing) {
+ if (!ref.current || isEditing) {
return;
}
- flatListRef.current.scrollToIndex(index);
+ ref.current.scrollToIndex(index);
}
/**
* Scroll to the bottom of the flatlist.
*
+ * @param {Object} ref
*/
-function scrollToBottom() {
- if (!flatListRef.current) {
+function scrollToBottom(ref) {
+ if (!ref.current) {
return;
}
- flatListRef.current.scrollToOffset({animated: false, offset: 0});
+ ref.current.scrollToOffset({animated: false, offset: 0});
}
-export {flatListRef, scrollToIndex, scrollToBottom};
+export {scrollToIndex, scrollToBottom};
diff --git a/src/libs/ReportScrollManager/index.native.js b/src/libs/ReportScrollManager/index.native.js
index 85a67f51e559..570d3a72acac 100644
--- a/src/libs/ReportScrollManager/index.native.js
+++ b/src/libs/ReportScrollManager/index.native.js
@@ -1,28 +1,28 @@
-import React from 'react';
-
-// This ref is created using React.createRef here because this function is used by a component that doesn't have access
-// to the original ref.
-const flatListRef = React.createRef();
-
/**
* Scroll to the provided index.
*
+ * @param {Object} ref
* @param {Object} index
*/
-function scrollToIndex(index) {
- flatListRef.current.scrollToIndex(index);
+function scrollToIndex(ref, index) {
+ if (!ref.current) {
+ return;
+ }
+
+ ref.current.scrollToIndex(index);
}
/**
* Scroll to the bottom of the flatlist.
*
+ * @param {Object} ref
*/
-function scrollToBottom() {
- if (!flatListRef.current) {
+function scrollToBottom(ref) {
+ if (!ref.current) {
return;
}
- flatListRef.current.scrollToOffset({animated: false, offset: 0});
+ ref.current.scrollToOffset({animated: false, offset: 0});
}
-export {flatListRef, scrollToIndex, scrollToBottom};
+export {scrollToIndex, scrollToBottom};
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index 34d6924dff54..1e3a297f08da 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -17,6 +17,7 @@ import DateUtils from '../DateUtils';
import TransactionUtils from '../TransactionUtils';
import * as ErrorUtils from '../ErrorUtils';
import * as UserUtils from '../UserUtils';
+import * as Report from './Report';
const chatReports = {};
const iouReports = {};
@@ -427,6 +428,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part
);
resetMoneyRequestInfo();
Navigation.dismissModal(chatReport.reportID);
+ Report.notifyNewAction(chatReport.reportID, payeeAccountID);
}
/**
@@ -733,6 +735,7 @@ function splitBill(participants, currentUserLogin, currentUserAccountID, amount,
resetMoneyRequestInfo();
Navigation.dismissModal();
+ Report.notifyNewAction(groupData.chatReportID, currentUserAccountID);
}
/**
@@ -763,6 +766,7 @@ function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccou
resetMoneyRequestInfo();
Navigation.dismissModal(groupData.chatReportID);
+ Report.notifyNewAction(groupData.chatReportID, currentUserAccountID);
}
/**
@@ -1227,6 +1231,7 @@ function sendMoneyElsewhere(report, amount, currency, comment, managerID, recipi
resetMoneyRequestInfo();
Navigation.dismissModal(params.chatReportID);
+ Report.notifyNewAction(params.chatReportID, managerID);
}
/**
@@ -1244,6 +1249,7 @@ function sendMoneyWithWallet(report, amount, currency, comment, managerID, recip
resetMoneyRequestInfo();
Navigation.dismissModal(params.chatReportID);
+ Report.notifyNewAction(params.chatReportID, managerID);
}
/**
@@ -1261,6 +1267,7 @@ function sendMoneyViaPaypal(report, amount, currency, comment, managerID, recipi
resetMoneyRequestInfo();
Navigation.dismissModal(params.chatReportID);
+ Report.notifyNewAction(params.chatReportID, managerID);
asyncOpenURL(Promise.resolve(), buildPayPalPaymentUrl(amount, recipient.payPalMeAddress, currency));
}
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 4cf90dbcb1ab..b49af54ad075 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -193,6 +193,21 @@ function subscribeToNewActionEvent(reportID, callback) {
};
}
+/**
+ * Notify the ReportActionsView that a new comment has arrived
+ *
+ * @param {String} reportID
+ * @param {Number} accountID
+ * @param {String} reportActionID
+ */
+function notifyNewAction(reportID, accountID, reportActionID) {
+ if (reportID !== newActionSubscriber.reportID) {
+ return;
+ }
+ const isFromCurrentUser = accountID === currentUserAccountID;
+ newActionSubscriber.callback(isFromCurrentUser, reportActionID);
+}
+
/**
* Add up to two report actions to a report. This method can be called for the following situations:
*
@@ -306,12 +321,7 @@ function addActions(reportID, text = '', file) {
successData,
failureData,
});
-
- // Notify the ReportActionsView that a new comment has arrived
- if (reportID === newActionSubscriber.reportID) {
- const isFromCurrentUser = lastAction.actorAccountID === currentUserAccountID;
- newActionSubscriber.callback(isFromCurrentUser, lastAction.reportActionID);
- }
+ notifyNewAction(reportID, lastAction.actorAccountID, lastAction.reportActionID);
}
/**
@@ -1436,12 +1446,7 @@ function showReportActionNotification(reportID, action) {
Navigation.navigate(ROUTES.getReportRoute(reportID));
},
});
-
- // Notify the ReportActionsView that a new comment has arrived
- if (reportID === newActionSubscriber.reportID) {
- const isFromCurrentUser = action.actorAccountID === currentUserAccountID;
- newActionSubscriber.callback(isFromCurrentUser, action.reportActionID);
- }
+ notifyNewAction(reportID, action.actorAccountID, action.reportActionID);
}
/**
@@ -1830,6 +1835,7 @@ export {
clearPolicyRoomNameErrors,
clearIOUError,
subscribeToNewActionEvent,
+ notifyNewAction,
showReportActionNotification,
addEmojiReaction,
removeEmojiReaction,
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index d3fb665ecb48..e9229698d3d2 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -39,6 +39,7 @@ import TaskHeader from '../../components/TaskHeader';
import MoneyRequestHeader from '../../components/MoneyRequestHeader';
import withNavigation, {withNavigationPropTypes} from '../../components/withNavigation';
import * as ComposerActions from '../../libs/actions/Composer';
+import ReportScreenContext from './ReportScreenContext';
const propTypes = {
/** Navigation route context info provided by react navigation */
@@ -134,6 +135,8 @@ class ReportScreen extends React.Component {
isBannerVisible: true,
};
this.firstRenderRef = React.createRef();
+ this.flatListRef = React.createRef();
+ this.reactionListRef = React.createRef();
}
componentDidMount() {
@@ -248,111 +251,118 @@ class ReportScreen extends React.Component {
const policy = this.props.policies[`${ONYXKEYS.COLLECTION.POLICY}${this.props.report.policyID}`];
return (
-
-
-
+
+
- {ReportUtils.isMoneyRequestReport(this.props.report) || isSingleTransactionView ? (
-
- ) : (
- Navigation.goBack(ROUTES.HOME)}
- personalDetails={this.props.personalDetails}
- report={this.props.report}
- />
- )}
+
+ {ReportUtils.isMoneyRequestReport(this.props.report) || isSingleTransactionView ? (
+
+ ) : (
+ Navigation.goBack(ROUTES.HOME)}
+ personalDetails={this.props.personalDetails}
+ report={this.props.report}
+ />
+ )}
- {ReportUtils.isTaskReport(this.props.report) && (
-
- )}
-
- {Boolean(this.props.accountManagerReportID) && ReportUtils.isConciergeChatReport(this.props.report) && this.state.isBannerVisible && (
-
- )}
- {
- const skeletonViewContainerHeight = event.nativeEvent.layout.height;
-
- // The height can be 0 if the component unmounts - we are not interested in this value and want to know how much space it
- // takes up so we can set the skeleton view container height.
- if (skeletonViewContainerHeight === 0) {
- return;
- }
- reportActionsListViewHeight = skeletonViewContainerHeight;
- this.setState({skeletonViewContainerHeight});
- }}
- >
- {this.isReportReadyForDisplay() && !isLoadingInitialReportActions && !isLoading && (
-
+ )}
+
+ {Boolean(this.props.accountManagerReportID) && ReportUtils.isConciergeChatReport(this.props.report) && this.state.isBannerVisible && (
+
)}
-
- {/* Note: The report should be allowed to mount even if the initial report actions are not loaded. If we prevent rendering the report while they are loading then
- we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
- {(!this.isReportReadyForDisplay() || isLoadingInitialReportActions || isLoading) && (
-
- )}
-
- {this.isReportReadyForDisplay() && (
- <>
- {
+ const skeletonViewContainerHeight = event.nativeEvent.layout.height;
+
+ // The height can be 0 if the component unmounts - we are not interested in this value and want to know how much space it
+ // takes up so we can set the skeleton view container height.
+ if (skeletonViewContainerHeight === 0) {
+ return;
+ }
+ reportActionsListViewHeight = skeletonViewContainerHeight;
+ this.setState({skeletonViewContainerHeight});
+ }}
+ >
+ {this.isReportReadyForDisplay() && !isLoadingInitialReportActions && !isLoading && (
+
- >
- )}
-
- {!this.isReportReadyForDisplay() && (
-
- )}
-
-
-
-
-
-
+ )}
+
+ {/* Note: The report should be allowed to mount even if the initial report actions are not loaded. If we prevent rendering the report while they are loading then
+ we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
+ {(!this.isReportReadyForDisplay() || isLoadingInitialReportActions || isLoading) && (
+
+ )}
+
+ {this.isReportReadyForDisplay() && (
+ <>
+
+ >
+ )}
+
+ {!this.isReportReadyForDisplay() && (
+
+ )}
+
+
+
+
+
+
+
);
}
}
diff --git a/src/pages/home/ReportScreenContext.js b/src/pages/home/ReportScreenContext.js
new file mode 100644
index 000000000000..2f79d6ae9432
--- /dev/null
+++ b/src/pages/home/ReportScreenContext.js
@@ -0,0 +1,4 @@
+import {createContext} from 'react';
+
+const ReportScreenContext = createContext();
+export default ReportScreenContext;
diff --git a/src/pages/home/report/ReactionList/ReactionListRefContext.js b/src/pages/home/report/ReactionList/ReactionListRefContext.js
deleted file mode 100644
index 12417b03bb15..000000000000
--- a/src/pages/home/report/ReactionList/ReactionListRefContext.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import React from 'react';
-
-const ReactionListRefContext = React.createContext();
-export default ReactionListRefContext;
diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js
index 56b78a73ae50..7d4535abc2c8 100644
--- a/src/pages/home/report/ReportActionItemMessageEdit.js
+++ b/src/pages/home/report/ReportActionItemMessageEdit.js
@@ -11,7 +11,6 @@ import themeColors from '../../../styles/themes/default';
import * as StyleUtils from '../../../styles/StyleUtils';
import Composer from '../../../components/Composer';
import * as Report from '../../../libs/actions/Report';
-import * as ReportScrollManager from '../../../libs/ReportScrollManager';
import openReportActionComposeViewWhenClosingMessageEdit from '../../../libs/openReportActionComposeViewWhenClosingMessageEdit';
import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager';
import EmojiPickerButton from '../../../components/EmojiPicker/EmojiPickerButton';
@@ -33,6 +32,7 @@ import Hoverable from '../../../components/Hoverable';
import useLocalize from '../../../hooks/useLocalize';
import useKeyboardState from '../../../hooks/useKeyboardState';
import useWindowDimensions from '../../../hooks/useWindowDimensions';
+import useReportScrollManager from '../../../hooks/useReportScrollManager';
const propTypes = {
/** All the data of the action */
@@ -75,6 +75,7 @@ const emojiButtonID = 'emojiButton';
const messageEditInput = 'messageEditInput';
function ReportActionItemMessageEdit(props) {
+ const reportScrollManager = useReportScrollManager();
const {translate} = useLocalize();
const {isKeyboardShown} = useKeyboardState();
const {isSmallScreenWidth} = useWindowDimensions();
@@ -186,11 +187,11 @@ function ReportActionItemMessageEdit(props) {
// Scroll to the last comment after editing to make sure the whole comment is clearly visible in the report.
if (props.index === 0) {
const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
- ReportScrollManager.scrollToIndex({animated: true, index: props.index}, false);
+ reportScrollManager.scrollToIndex({animated: true, index: props.index}, false);
keyboardDidHideListener.remove();
});
}
- }, [props.action.reportActionID, debouncedSaveDraft, props.index, props.reportID]);
+ }, [props.action.reportActionID, debouncedSaveDraft, props.index, props.reportID, reportScrollManager]);
/**
* Save the draft of the comment to be the new comment message. This will take the comment out of "edit mode" with
@@ -306,7 +307,7 @@ function ReportActionItemMessageEdit(props) {
style={[styles.textInputCompose, styles.flex1, styles.bgTransparent]}
onFocus={() => {
setIsFocused(true);
- ReportScrollManager.scrollToIndex({animated: true, index: props.index}, true);
+ reportScrollManager.scrollToIndex({animated: true, index: props.index}, true);
ComposerActions.setShouldShowComposeInput(false);
}}
onBlur={(event) => {
diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
index c3288c3eeb6a..8e48f06c3528 100644
--- a/src/pages/home/report/ReportActionsList.js
+++ b/src/pages/home/report/ReportActionsList.js
@@ -4,7 +4,6 @@ import Animated, {useSharedValue, useAnimatedStyle, withTiming} from 'react-nati
import _ from 'underscore';
import InvertedFlatList from '../../../components/InvertedFlatList';
import compose from '../../../libs/compose';
-import * as ReportScrollManager from '../../../libs/ReportScrollManager';
import styles from '../../../styles/styles';
import * as ReportUtils from '../../../libs/ReportUtils';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
@@ -21,6 +20,7 @@ import CONST from '../../../CONST';
import reportPropTypes from '../../reportPropTypes';
import networkPropTypes from '../../../components/networkPropTypes';
import withLocalize from '../../../components/withLocalize';
+import useReportScrollManager from '../../../hooks/useReportScrollManager';
const propTypes = {
/** Position of the "New" line marker */
@@ -78,6 +78,7 @@ function keyExtractor(item) {
}
function ReportActionsList(props) {
+ const reportScrollManager = useReportScrollManager();
const opacity = useSharedValue(0);
const animatedStyles = useAnimatedStyle(() => ({
opacity: opacity.value,
@@ -152,7 +153,7 @@ function ReportActionsList(props) {
-
-
-
+
@@ -368,5 +366,6 @@ class ReportActionsView extends React.Component {
ReportActionsView.propTypes = propTypes;
ReportActionsView.defaultProps = defaultProps;
+ReportActionsView.contextType = ReportScreenContext;
export default compose(Performance.withRenderTrace({id: ' rendering'}), withWindowDimensions, withNavigationFocus, withLocalize, withNetwork())(ReportActionsView);
diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js
index f7d78cc6b06b..4655427ca3e6 100644
--- a/src/pages/iou/steps/MoneyRequestConfirmPage.js
+++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js
@@ -10,7 +10,6 @@ import ScreenWrapper from '../../../components/ScreenWrapper';
import styles from '../../../styles/styles';
import Navigation from '../../../libs/Navigation/Navigation';
import ROUTES from '../../../ROUTES';
-import * as ReportScrollManager from '../../../libs/ReportScrollManager';
import * as IOU from '../../../libs/actions/IOU';
import compose from '../../../libs/compose';
import * as ReportUtils from '../../../libs/ReportUtils';
@@ -196,14 +195,8 @@ function MoneyRequestConfirmPage(props) {
iouAmount={props.iou.amount}
iouComment={props.iou.comment}
iouCurrencyCode={props.iou.currency}
- onConfirm={(selectedParticipants) => {
- createTransaction(selectedParticipants);
- ReportScrollManager.scrollToBottom();
- }}
- onSendMoney={(paymentMethodType) => {
- sendMoney(paymentMethodType);
- ReportScrollManager.scrollToBottom();
- }}
+ onConfirm={createTransaction}
+ onSendMoney={sendMoney}
onSelectParticipant={(option) => {
const newParticipants = _.map(props.iou.participants, (participant) => {
if (participant.accountID === option.accountID) {