-
Notifications
You must be signed in to change notification settings - Fork 3k
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
[HOLD Manual Requests][$1000] IOU Details page reload again after opening them same IOU #14399
Comments
I was able to reproduce this one on staging on my iPhone. The spinner after the page had already loaded definitely feels like a bug. |
Job added to Upwork: https://www.upwork.com/jobs/~01f5892f51566457de |
Current assignee @tjferriss is eligible for the External assigner, not assigning anyone new. |
Triggered auto assignment to Contributor-plus team member for initial proposal review - @mollfpr ( |
Triggered auto assignment to @dangrous ( |
ProposalEvery time we open the IOU details, we perform a read request to the API. When we open back from the background, there is 2 write requests happening, ReconnectApp and OpenReport. If we take a look at the API.read implementation, it will wait for the write request to be completed first, so the optimistic data won't be applied until the write request is done. We can verify this with waiting for a few seconds before opening the IOU details after coming back from background. The simplest way I think is to apply the optimistic data first on read function. function read(command, apiCommandParameters, onyxData) {
+ // Optimistically update Onyx
+ if (onyxData.optimisticData) {
+ Onyx.update(onyxData.optimisticData);
+ }
// Ensure all write requests on the sequential queue have finished responding before running read requests.
// Responses from read requests can overwrite the optimistic data inserted by
// write requests that use the same Onyx keys and haven't responded yet.
SequentialQueue.waitForIdle().then(() => makeRequestWithSideEffects(command, apiCommandParameters, onyxData, CONST.API_REQUEST_TYPE.READ));
} With this change, we will do the onyx update twice, once in read, once in makeRequestWithSideEffects. But looking at the comment, I am not sure with this approach. Another approach I would like to propose is to create a new Onyx key to let a component knows whether a read request is pending or not. diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 77c3f0396..c4b7227fb 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -175,4 +175,7 @@ export default {
// Whether we should show the compose input or not
SHOULD_SHOW_COMPOSE_INPUT: 'shouldShowComposeInput',
+
+ // Set when the read request is waiting for all write requests to be finished
+ IS_PENDING_READ: 'isPendingRead',
};
diff --git a/src/libs/API.js b/src/libs/API.js
index d86fc099a..a917f1b18 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -4,6 +4,7 @@ import * as Request from './Request';
import * as SequentialQueue from './Network/SequentialQueue';
import pkg from '../../package.json';
import CONST from '../CONST';
+import ONYXKEYS from '../ONYXKEYS';
/**
* All calls to API.write() will be persisted to disk as JSON with the params, successData, and failureData.
@@ -105,10 +106,14 @@ function makeRequestWithSideEffects(command, apiCommandParameters = {}, onyxData
* @param {Object} [onyxData.failureData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode !== 200.
*/
function read(command, apiCommandParameters, onyxData) {
+ Onyx.set(ONYXKEYS.IS_PENDING_READ, true);
// Ensure all write requests on the sequential queue have finished responding before running read requests.
// Responses from read requests can overwrite the optimistic data inserted by
// write requests that use the same Onyx keys and haven't responded yet.
- SequentialQueue.waitForIdle().then(() => makeRequestWithSideEffects(command, apiCommandParameters, onyxData, CONST.API_REQUEST_TYPE.READ));
+ SequentialQueue.waitForIdle().then(() => {
+ Onyx.set(ONYXKEYS.IS_PENDING_READ, false);
+ makeRequestWithSideEffects(command, apiCommandParameters, onyxData, CONST.API_REQUEST_TYPE.READ);
+ });
}
export {
diff --git a/src/pages/iou/IOUDetailsModal.js b/src/pages/iou/IOUDetailsModal.js
index ab600cfb5..00e2ede54 100644
--- a/src/pages/iou/IOUDetailsModal.js
+++ b/src/pages/iou/IOUDetailsModal.js
@@ -165,7 +165,7 @@ class IOUDetailsModal extends Component {
title={this.props.translate('common.details')}
onCloseButtonPress={Navigation.dismissModal}
/>
- {this.props.iou.loading ? <ActivityIndicator color={themeColors.text} /> : (
+ {(this.props.iou.loading || this.props.isPendingRead) ? <ActivityIndicator color={themeColors.text} /> : (
<View style={[styles.flex1, styles.justifyContentBetween]}>
<ScrollView contentContainerStyle={styles.iouDetailsContainer}>
<IOUPreview
@@ -225,5 +225,8 @@ export default compose(
key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${route.params.chatReportID}`,
canEvict: false,
},
+ isPendingRead: {
+ key: ONYXKEYS.IS_PENDING_READ,
+ },
}),
)(IOUDetailsModal);
Result 329005.t.mp4 |
Looks like something related to As a reminder, please make sure that all proposals are not workarounds and that any and all attempt to fix the issue holistically have been made before proceeding with a solution. Proposals to change our Feel free to drop a note in #expensify-open-source with any questions. |
@bernhardoj Thanks for the proposal, but I think your proposal is over-engineered. Do you think this issue can be done without changing the API function? |
@mollfpr I think we can, but I personally don't like the solution. So here is the idea. I believe we will show the loading every time we open the details page. So, to make sure that the
I am sure people will confuse when read this 😅. ========= |
Yeah, that's not ideal and easy in the eyes. + {(this.props.iou.loading || this.props.isPendingRead) ? <ActivityIndicator color={themeColors.text} /> : ( IMO having a global loading state will cause unnecessary rerender. For example, if we have another API read after the |
Ah yeah, good point. I didn't realized that. I will try to think for another approach. 👍 |
ProposalRoot causeFor
When we get from background, there are two request that will be triggered This is a regression from #14028 , @mollfpr you already mentioned this case
Solution 1we can handle loading in state in the diff --git a/src/pages/iou/IOUDetailsModal.js b/src/pages/iou/IOUDetailsModal.js
index 4da6d6792f..699c230089 100644
--- a/src/pages/iou/IOUDetailsModal.js
+++ b/src/pages/iou/IOUDetailsModal.js
@@ -85,6 +85,14 @@ const defaultProps = {
};
class IOUDetailsModal extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ loading: true,
+ };
+ }
+
componentDidMount() {
if (this.props.network.isOffline) {
return;
@@ -94,6 +102,15 @@ class IOUDetailsModal extends Component {
}
componentDidUpdate(prevProps) {
+ // Update state when there is a change ,in the iou loading prop , between prevProps and actual props
+ if (prevProps.iou !== this.props.iou) {
+ const prevLoading = lodashGet(prevProps.iou, 'loading');
+ const currentLoading = lodashGet(this.props.iou, 'loading');
+
+ if (prevLoading !== currentLoading && prevLoading === this.state.loading) {
+ this.setState({loading: currentLoading});
+ }
+ }
if (!prevProps.network.isOffline || this.props.network.isOffline) {
return;
}
@@ -165,7 +182,7 @@ class IOUDetailsModal extends Component {
title={this.props.translate('common.details')}
onCloseButtonPress={Navigation.dismissModal}
/>
- {this.props.iou.loading ? <View style={styles.flex1}><FullScreenLoadingIndicator /></View> : (
+ {!this.props.network.isOffline && this.state.loading ? <View style={styles.flex1}><FullScreenLoadingIndicator /></View> : (
<View style={[styles.flex1, styles.justifyContentBetween]}>
<ScrollView contentContainerStyle={styles.iouDetailsContainer}>
<IOUPreview
Solution 2we can display only the loading indicator when we don't have data in onyx and the read request hasn't yet finished diff --git a/src/pages/iou/IOUDetailsModal.js b/src/pages/iou/IOUDetailsModal.js
index 4da6d6792f..53bd37a2e8 100644
--- a/src/pages/iou/IOUDetailsModal.js
+++ b/src/pages/iou/IOUDetailsModal.js
@@ -157,7 +157,7 @@ class IOUDetailsModal extends Component {
return (
<ScreenWrapper>
<FullPageNotFoundView
- shouldShow={_.isEmpty(this.props.iouReport)}
+ shouldShow={!this.props.iou.loading && _.isEmpty(this.props.iouReport)}
subtitleKey="notFound.iouReportNotFound"
onBackButtonPress={Navigation.dismissModal}
>
@@ -165,7 +165,7 @@ class IOUDetailsModal extends Component {
title={this.props.translate('common.details')}
onCloseButtonPress={Navigation.dismissModal}
/>
- {this.props.iou.loading ? <View style={styles.flex1}><FullScreenLoadingIndicator /></View> : (
+ {this.props.iou.loading && _.isEmpty(this.props.iouReport) ? <View style={styles.flex1}><FullScreenLoadingIndicator /></View> : (
<View style={[styles.flex1, styles.justifyContentBetween]}>
<ScrollView contentContainerStyle={styles.iouDetailsContainer}>
<IOUPreview
|
@fedirjh Thanks for the proposal. I did have concerns about this issue, but my expected result was to only show the loading indicator where we don't have the Onyx data. I believe solution 2 will be updating |
@mollfpr Yes , the loading indicator will only show when there is no data in Onyx and there is a read request that is being executed , once the request is fulfilled then it will either show
Also I don't think this is the expected result, I think that once we have data in onyx then we don't have to show loading indicator anymore , this behaviour (OP expected result) will be different in offline mode , there should be no indicator in offline mode , in other words if we expect loading indicator to show then the |
Reading through, I have a question - does it make sense to have loading states in both the iou and the modal (which would happen if we implemented this)? It seems a bit odd... And iou.loading is only used here, and in |
@dangrous the loading states are different, the
@mollfpr I just deleted my proposal (you can check it in edit history) , I don't think this is a right pattern to reset EditI will provide another proposal , we can reset the Solution 3diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 3cbb5a2b01..d96c97726f 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -458,6 +458,8 @@ function readOldestAction(reportID, oldestActionSequenceNumber) {
* @param {Number} iouReportID
*/
function openPaymentDetailsPage(chatReportID, iouReportID) {
+ // Resets the IOU loading state before we enqueue request
+ Onyx.merge(ONYXKEYS.IOU, {loading: true});
API.read('OpenPaymentDetailsPage', {
reportID: chatReportID,
iouReportID, Solution 4we can also execute the diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 3cbb5a2b01..5c84665c49 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -458,19 +458,20 @@ function readOldestAction(reportID, oldestActionSequenceNumber) {
* @param {Number} iouReportID
*/
function openPaymentDetailsPage(chatReportID, iouReportID) {
+ // Resets the IOU loading state before enqueueing request
+ Onyx.update([
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.IOU,
+ value: {
+ loading: true,
+ },
+ },
+ ]);
API.read('OpenPaymentDetailsPage', {
reportID: chatReportID,
iouReportID,
}, {
- optimisticData: [
- {
- onyxMethod: CONST.ONYX.METHOD.MERGE,
- key: ONYXKEYS.IOU,
- value: {
- loading: true,
- },
- },
- ],
successData: [
{
onyxMethod: CONST.ONYX.METHOD.MERGE, |
Proposaldiff --git a/src/libs/API.js b/src/libs/API.js
index d86fc099a0..850bcb626a 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -104,11 +104,16 @@ function makeRequestWithSideEffects(command, apiCommandParameters = {}, onyxData
* @param {Object} [onyxData.successData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode === 200.
* @param {Object} [onyxData.failureData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode !== 200.
*/
-function read(command, apiCommandParameters, onyxData) {
+function read(command, apiCommandParameters = {}, onyxData = {}) {
// Ensure all write requests on the sequential queue have finished responding before running read requests.
// Responses from read requests can overwrite the optimistic data inserted by
// write requests that use the same Onyx keys and haven't responded yet.
- SequentialQueue.waitForIdle().then(() => makeRequestWithSideEffects(command, apiCommandParameters, onyxData, CONST.API_REQUEST_TYPE.READ));
+
+ // Optimistically update Onyx
+ if (onyxData.optimisticData) {
+ Onyx.update(onyxData.optimisticData);
+ }
+ SequentialQueue.waitForIdle().then(() => makeRequestWithSideEffects(command, apiCommandParameters, _.omit(onyxData, 'optimisticData'), CONST.API_REQUEST_TYPE.READ));
}
export { RCAI think @fedirjh did a great job explaining the root cause and I agree with his findings. However I'm not okay with any of the proposed solutions above except for @bernhardoj very first proposal which was almost just okay, he just forget to fix one thing and that's the double onyx update. Personally I think the IOU logic is implemented correctly, so any change there is going to only be a patch especially since the same issue may happen in other places as well. The fix is to apply the Also to address the comments and the concern about data conflicts, the comments state that "Responses from read requests can overwrite the optimistic data". So the issue is actually on the returned data from the server that may cause a conflict and we are respecting that as the request is still queued. FWIW |
If we agree about this statement then we should close this issue without any changes.
You propose a global solution so you have to test all other usages of concurrent read and write requests . Also this has to be discussed wider in slack.
Nope we are not respecting that, write request can have optimistic data too. So if we target same key with two queued requests (read and write) then what will happen ? |
@tjferriss Tom and I will take this from you, if you don't mind, and we'll wrap it up in the Manual Requests project. |
Cool, fair enough. Works for me! 👍 |
|
Ah okay cool - so to confirm, we're going to close this issue, and we'll revisit the details as part of Manual Requests? If so, do we still pay out for reporting? I don't think we'd need any of the other jobs. Just want to make sure we're on the same page! |
I think we keep this issue open and on hold for manual requests, then revisit to implement it. Payments will be handled after it's on prod. |
Totally, nothing to do for the time being. Let's keep it open and hold. |
Sounds good! |
On hold for manual requests. Going to pop it on |
Still on hold |
Still on hold, I think we will be able to open it up again soon though! |
Yes indeed! Several weeks at the least. |
@mountiny @Julesssss @JmillsExpensify how do we feel about this one now? We have the skeleton UI loading in the expense/iou report screen and the request detailed thread. One improvement might be to include the page header(s) in the skeleton UI, right now I think it's just the comment sections? |
Yes, but I think we should create a fresh issue for that one. This issue is about component we wont support anymore. |
Cool, so do we just close this out instead? |
I think we can, I am not even sure if we need the updated skeleton, ti would not work when using URL and fetching the data fresh as then we have no clue what type of a report we have so we could only potentially do this when navigating from within the app, but I dont think its something we have to focus on |
Okay I'll close this for now, and if we end up needing to reopen, we can do that. |
If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!
Action Performed:
Expected Result:
After enter foreground mode and tap the IOU, IOU Detail page should show loading and load the IOU Detail.
Actual Result:
IOU Detail page doesn't show loading after bringing to foreground then after a brief moment loading indicator appears
Workaround:
unknown
Platforms:
Which of our officially supported platforms is this issue occurring on?
Version Number: 1.2.56-0
Reproducible in staging?: y
Reproducible in production?: y
If this was caught during regression testing, add the test name, ID and link from TestRail:
Email or phone of affected tester (no customers):
Logs: https://stackoverflow.com/c/expensify/questions/4856
Notes/Photos/Videos:
RPReplay_Final1674041550.MP4
HCZK7715.MP4
Expensify/Expensify Issue URL:
Issue reported by: @hungvu193
Slack conversation: https://expensify.slack.com/archives/C049HHMV9SM/p1674042038386489
View all open jobs on GitHub
Upwork Automation - Do Not Edit
The text was updated successfully, but these errors were encountered: