Expensify / App

Welcome to New Expensify: a complete re-imagination of financial collaboration, centered around chat. Help us build the next generation of Expensify by sharing feedback and contributing to the code.
https://new.expensify.com
MIT License
3.34k stars 2.77k forks source link

[ON HOLD][$2000] Android - Workspace is still selectable even after 'Create room' button has been pressed #21577

Closed kbecciv closed 9 months ago

kbecciv commented 1 year ago

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Action Performed:

  1. Go to android
  2. Click on FAB menu
  3. Click on New Room
  4. Enter room name > select a workspace
  5. (Important step )> Click on create room and immediately again select the workspace dropdown and notice that it is still clickable

Expected Result:

Workspace should not be selected when the 'create room' button has been pressed

Actual Result:

Workspace is still selectable even after 'Create room' button has been pressed

Workaround:

Unknown

Platforms:

Which of our officially supported platforms is this issue occurring on?

Version Number: 1.3.29-11 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: Any additional supporting documentation

https://github.com/Expensify/App/assets/93399543/9d5fd6ae-444f-4d53-ae03-dcf7a01e3060

https://github.com/Expensify/App/assets/93399543/d299d0d5-70ce-4586-ab4b-8fbb36f34c8f

Expensify/Expensify Issue URL: Issue reported by: @priya-zha Slack conversation: https://expensify.slack.com/archives/C049HHMV9SM/p1687430588059259

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~011eacb8f4d46da0d6
  • Upwork Job ID: 1673652478476353536
  • Last Price Increase: 2023-07-19
melvin-bot[bot] commented 1 year ago

@johncschuster @fedirjh this issue is now 4 weeks old and preventing us from maintaining WAQ, can you:

Thanks!

melvin-bot[bot] commented 1 year ago

Current assignee @fedirjh is eligible for the Internal assigner, not assigning anyone new.

johncschuster commented 1 year ago

@fedirjh bump! Do you feel the proposals are sufficient, or should we take this internal?

fedirjh commented 1 year ago

@kmwamasali Do you have any update?

Could you tell me which should be improved in my proposal https://github.com/Expensify/App/issues/21577#issuecomment-1641026338? Thank you.

@ginsuma Your solution of using an empty view which triggers the form submission using onLayout is a workaround for me. I am not sure, why didn't you use a hook instead of that approach ?

kmwamasali commented 1 year ago

@fedirjh I managed to stop clicks using the css pointer-events: none but it required duplicating a huge piece of the form code block to introduce the style conditionally(easier suggestions to do this very welcome) . My attempts at using preventDefault to stop event bubbling didn't work. My proposal would be to introduce a loading screen/overlay as it would also improve user experience since it's kind of akward to stare and wait after you've pressed the button with no indication that something is happening ?

ginsuma commented 1 year ago

Maybe this one will help us https://github.com/Expensify/App/issues/16935. In the end, I think there is a blocking in the navigation after submitting the form. Although our design is offline first, but the screen doesn't navigate immediately.

fedirjh commented 1 year ago

Maybe this one will help us https://github.com/Expensify/App/issues/16935.

I am not sure that will fix the problem globally. Looking at the proposal on the other issue, it seems it will fix that specific case.

I agree that there is a blocking in the navigation after Form submission, and it seems that is related to Onyx.SET method.

pasyukevich commented 1 year ago

Hi, I am from Callstack - an expert contributor group I start to investigate this issue

kmwamasali commented 1 year ago

@fedirjh do you think a loading screen/mask for forms in general would be an okay idea

pasyukevich commented 1 year ago

I continue my further investigation by looking for a proper fix.

fedirjh commented 1 year ago

do you think a loading screen/mask for forms in general would be an okay idea

@kmwamasali This doesn’t look valid case, on all platforms upon submitting a Form, it will instantly navigate. This issue only affects Android due to Onyx blocking behavior. Displaying the loading screen won't be consistent on all platforms and that’s not a desired behavior.


Thanks for the update @pasyukevich.

Thanos30 commented 1 year ago

@fedirjh Could you please explain to me how Onyx is blocking the form state update?

I believe the navigation is what's blocking the state update, and that's why I handle the navigation asynchronously in my proposal.

Thanks 🙏

fedirjh commented 1 year ago

Could you please explain to me how Onyx is blocking the form state update?

I believe the navigation is what's blocking the state update

@Thanos30 The Onyx.SET method is causing a block in the Thread, which, in turn, blocks all other executions, including navigation and any other state updates.

https://github.com/Expensify/App/blob/cf90c1358463c959096fdd0670a1ef104701f1a5/src/libs/actions/Report.js#L1300

For further context, please refer to this issue:

Thanos30 commented 1 year ago

Thank you for the context @fedirjh. In my proposal, I am handling the whole creating the workspace method, including Onyx.SET and the navigation, asynchronously, which I still believe is the best case scenario here. While I was checking the delay though (debugging the issue) I noticed that the navigation action was fired, and the delay was on that part and not the SET on Onyx. I might be wrong though.

Is there any reason why you don't agree with this? I believe in such cases we could use asynchronous code, and if I am not mistaken is good practice too.

Thank you for your feedback once again 🙏

pasyukevich commented 1 year ago

I finished my investigation. Preparing the proposal

johncschuster commented 1 year ago

@fedirjh given we're at the WAQ threshold, what do you think we should do to get this over the line?

fedirjh commented 1 year ago

Awaiting an update from @pasyukevich

pasyukevich commented 1 year ago

Proposal

Please re-state the problem that we are trying to solve in this issue.

Input fields with selection are clickable after submission of the form

What is the root cause of that problem?

onSubmit function consists of blocking thread code and delaying the transition to the next string. While the thread is blocked we are still able to interact with pickers. To fix this we need to block the interaction with the form before calling the submit function.

What changes do you think we should make in order to solve the problem?

Add isFormSubmitted state and set it to true at the submit function Wrap form content to block interaction based on isFormSubmitted state

<View style={{pointerEvents: isFormSubmitted ? 'none':'auto'}}>
        {childrenWrapperWithProps(_.isFunction(children) ? children({inputValues}) : children)}
        </View>

Add useEffect that will be triggered on isFormSubmitted change. To establish that onSubmit function will non-blocking for repaint on the state changes we need to put onSubmit in the requestAnimationFrame, which will execute callback at the optimal time for smooth performance. (Callback and promise are not achieving the expected result in this case)

useEffect(() => {
      if (!isFormSubmitted) {
        return;
      }

     requestAnimationFrame(() => {
      onSubmit(inputValues);
     });

  }, [onSubmit, inputValues, isFormSubmitted]);

Update submit button loading state with isFormSubmitted isLoading={props.formState.isLoading || isFormSubmitted}

What alternative solutions did you explore?

setTimeout can be here as a working option but is not preferable as it does not guarantee that the callback will be executed at the specified time in sync with the screen refresh rate

ginsuma commented 1 year ago

@pasyukevich, Could you share a branch where you applied your proposal? I just want to test requestAnimationFrame in your solution. Thank you.

Thanos30 commented 1 year ago

@pasyukevich Isn't this basically the same solution that I proposed? The only 2 differences is:

1) The use of requestAnimationFrame instead of setTimeout, which, based on here, I believe it's better to use setTimeout and trigger the creation of the workspace and the navigation immediately.

2) The useEffect hook instead of altering the onSubmit callback. Isn't it much better to alter the onSubmit callback to use requestAnimationFrame (as you did in your proposal) on that specific code block that causes the issue, instead of making a useEffect hook that will be triggered on every change we do on the workspace creation page?

pasyukevich commented 1 year ago

@Thanos30

  1. According to the docs requestAnimationFrame will be called after flushing the frames. In this case, this guarantee that fields will be blocked from interactions
  2. Here useEffect is used to schedule onSubmit only after the isFormSubmitted is updated. Also, I believe that better to cover this case for other forms too.
Thanos30 commented 1 year ago

@pasyukevich Gotcha, thanks for the explanation 🙏

If the submission action, and therefore the navigation runs in requestAnimationFrame or setTimeout , it's gonna go on the background and run on the specified time, and only after the stack is cleared, and in this case, it's not ever gonna block the state update, so the faster the better, even if we are talking about miliseconds here.

Since this is happening on the background, there is no need to submit the form with a state update either, as these will run the moment the code is finished updating the state.

Also, not sure how handy it is to have a hook that will re-trigger on every input change, even if it's just returning nothing. This will happen on the Form component, so it will happen on all the forms throughout the app.

I liked the thought though!

johncschuster commented 1 year ago

@fedirjh now that we've received an update from @pasyukevich, what's the next step here?

melvin-bot[bot] commented 1 year ago

@johncschuster, @fedirjh 6 days overdue. This is scarier than being forced to listen to Vogon poetry!

melvin-bot[bot] commented 1 year ago

@johncschuster, @fedirjh Now this issue is 8 days overdue. Are you sure this should be a Daily? Feel free to change it!

melvin-bot[bot] commented 1 year ago

@johncschuster, @fedirjh 12 days overdue now... This issue's end is nigh!

johncschuster commented 1 year ago

Bumped in Slack.

fedirjh commented 1 year ago

Hey there

Thanks @pasyukevich for the proposal. I would like to share some concerns I have regarding the solution.

Add useEffect that will be triggered on isFormSubmitted change.

I find this approach rather tricky to follow, and I don't believe it's a wise choice for a widely-used component like the Form. Based on my testing, I found that this hook can trigger multiple times during the form submission process, resulting in duplicate submissions. For instance, when creating a new room, the hook triggered 3 times. Consequently, 3 rooms were created, of which the additional 2 rooms displayed errors.

Screenshot 2023-08-29 at 1 42 51 PM Screenshot 2023-08-29 at 1 43 17 PM
Code Diff ```diff diff --git a/src/components/Form.js b/src/components/Form.js index eb6945f6ec..0226123272 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -103,6 +103,7 @@ const defaultProps = { function Form(props) { const [errors, setErrors] = useState({}); + const [isFormSubmitted, setIsFormSubmitted] = useState(false); const [inputValues, setInputValues] = useState({...props.draftValues}); const formRef = useRef(null); const formContentRef = useRef(null); @@ -206,10 +207,22 @@ function Form(props) { return; } - // Call submit handler - onSubmit(inputValues); + setIsFormSubmitted(true); }, [props.formState, onSubmit, inputRefs, inputValues, onValidate, touchedInputs, props.network.isOffline, props.enabledWhenOffline]); + useEffect(() => { + if (!isFormSubmitted) { + return; + } + + requestAnimationFrame(() => { + // Call submit handler + console.log('submitting form') + onSubmit(inputValues); + }); + + }, [onSubmit, inputValues, isFormSubmitted]); + /** * Loops over Form's children and automatically supplies Form props to them * @@ -368,7 +381,7 @@ function Form(props) { 0 || Boolean(errorMessage) || !_.isEmpty(props.formState.errorFields)} - isLoading={props.formState.isLoading} + isLoading={props.formState.isLoading || isFormSubmitted} message={_.isEmpty(props.formState.errorFields) ? errorMessage : null} onSubmit={submit} footerContent={props.footerContent} @@ -403,6 +416,7 @@ function Form(props) { ), [ + isFormSubmitted, childrenWrapperWithProps, errors, formContentRef, diff --git a/src/libs/Permissions.js b/src/libs/Permissions.js index 7f52c41ad0..f085991f33 100644 --- a/src/libs/Permissions.js +++ b/src/libs/Permissions.js @@ -7,7 +7,7 @@ import CONST from '../CONST'; * @returns {Boolean} */ function canUseAllBetas(betas) { - return _.contains(betas, CONST.BETAS.ALL); + return true || _.contains(betas, CONST.BETAS.ALL); } /** ```

Here useEffect is used to schedule onSubmit only after the isFormSubmitted is updated. Also, I believe that it is better to cover this case for other forms too.

It should be noted that the use of requestAnimationFrame is geared towards scheduling onSubmit after the state update. Removing this code (requestAnimationFrame) would engender an infinite re-running of the hook. Thus, the scheduling aspect is intrinsically linked to the utilization of requestAnimationFrame.

I would suggest an alternative approach to this. Instead of using a hook, we can incorporate the scheduling of the form's onSubmit call directly after the state update. This can be done within the submit method itself, as demonstrated below. This approach not only blocks the form but also ensures that submission takes place immediately after the state update:

setIsFormSubmitted(true);
requestAnimationFrame(() => {
    // Call submit handler
    onSubmit(inputValues);
});

setTimeout can be here as a working option but is not preferable as it does not guarantee that the callback will be executed at the specified time in sync with the screen refresh rate

In response to the suggestion of using requestAnimationFrame instead of setTimeout, I believe it would be more sensible to immediately execute the form submission. As the submission process itself blocks the thread, it might not be necessary to wait for the frame rate and utilize requestAnimationFrame.


Lastly, I would like to raise the consideration of resetting the Form state (isFormSubmitted) in the event of a submission failure. In some cases, the server might respond with errors related to specific inputs. Regrettably, this aspect has not been addressed in the initial proposal.

pasyukevich commented 1 year ago

@fedirjh Thanks for review. I will update according to your comments

Can you share more about cases when we will receive an error on form submission? (I cannot find any resources or code that handles it) As far as I know, we are using an optimistic approach.

Thanos30 commented 1 year ago

@fedirjh Correct me if I am wrong please, but when I posted the first proposal here, your only concern was the use of setTimeout which you were not fond of. My proposal was simply running the submit action in Form using setTimeout which solved the issue. Is there something I am missing here?

Kinda feeling a bit aggrieved here since I was here 2 months ago with a proposal that worked and was trying to figure out a way to not use setTimeout or its alternatives as you suggested, but it seems that it is acceptable now.

Thanks in advance 🙏

fedirjh commented 1 year ago

Can you share more about cases when we will receive an error on form submission?

@pasyukevich In offline mode, some forms are disabled, such as the Add Debit Card form. You can access it through this route:/settings/wallet/add-debit-card. To enable it, you should opt into the useWallet beta.

As far as I know, we are using an optimistic approach.

Regarding the Add Debit Card form, it's worth noting that the data is sensitive and is not stored on the client side. As a result, we don't strictly adhere to the optimistic approach in this case.


but it seems that it is acceptable now.

@Thanos30 Thank you for bringing up your concerns. It's important to clarify that the use of setTimeout has not been accepted yet. My previous response was based on my review, where I mentioned a preference for setTimeout over requestAnimationFrame in case a decision had to be made.

I don't see requestAnimationFrame as substantially different from setTimeout. While requestAnimationFrame waits for the next frame, its timing is not entirely deterministic and may experience delays during periods of high system load or FPS drops. In contrast, setTimeout is more likely to trigger at the specified time, providing a more predictable behavior. The final decision regarding its acceptance still needs to be made.

Thank you for your understanding.

melvin-bot[bot] commented 1 year ago

@johncschuster, @fedirjh Still overdue 6 days?! Let's take care of this!

fedirjh commented 1 year ago

cc @pasyukevich Have you had a chance to provide an update?

melvin-bot[bot] commented 1 year ago

@johncschuster, @fedirjh Whoops! This issue is 2 days overdue. Let's get this updated quick!

pasyukevich commented 1 year ago

I will post an update here today

pasyukevich commented 1 year ago

Is it still reproducible?

While checking another solution I found that with current main delay has become small and is now the same as on the iOS platform.

melvin-bot[bot] commented 1 year ago

@johncschuster, @fedirjh 6 days overdue. This is scarier than being forced to listen to Vogon poetry!

johncschuster commented 1 year ago

@pasyukevich I'm having a hard time understanding what you mean in your comment above. Are you saying this behavior is still reproducible, or is it no longer reproducible?

pasyukevich commented 1 year ago

@johncschuster Before, I was able to reproduce this issue with a long delay, which is similar to that in the reported video. We have the point that this issue was exclusive to Android due to Onyx's blocking behavior. (https://github.com/Expensify/App/issues/21577#issuecomment-1665877050)

Currently, this delay has significantly reduced and is consistent across platforms, including iOS. During this brief delay, between form submission and redirection to the next screen, users can still interact with the form fields on both iOS and Android.

Should we address this on both platforms or considering the minimal delay, should we not categorize it as a bug?

melvin-bot[bot] commented 12 months ago

@johncschuster, @fedirjh Huh... This is 4 days overdue. Who can take care of this?

johncschuster commented 12 months ago

@pasyukevich I think we can categorize this as not a bug. I believe we have a separate project going to address overall performance that will likely resolve the delay.

fedirjh commented 11 months ago

Looks like we have refactored the Form to use hook in https://github.com/Expensify/App/pull/27025 and there will be some migrations of all old forms including this one.

I believe we have a separate project going to address overall performance that will likely resolve the delay.

Maybe we should put this on hold?

melvin-bot[bot] commented 11 months ago

@johncschuster, @fedirjh Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

fedirjh commented 11 months ago

Little bump @johncschuster for https://github.com/Expensify/App/issues/21577#issuecomment-1741525593

johncschuster commented 11 months ago

Thanks for the bump! That sounds good to me! Are we putting this on hold for #27025, or is there a different issue I can link to?

johncschuster commented 11 months ago

I've put the issue on hold. @fedirjh, can you confirm I have the right issue for the hold when you get a moment? Thank you!

johncschuster commented 11 months ago

Still holding.

fedirjh commented 11 months ago

can you confirm I have the right issue for the hold when you get a moment?

hey @johncschuster It should be on hold for https://github.com/Expensify/App/issues/25397

fedirjh commented 11 months ago

Still on hold.

fedirjh commented 11 months ago

@johncschuster The create room flow has been changed. It now uses a push to page for workspace selection, which seems to improve the UX and fix the bug for me. @kbecciv could you please retest the issue?

https://github.com/Expensify/App/assets/36869046/7ea18673-c21f-415b-8d6b-0b0771366227