Skip to content
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

Disables the Button during onPress call in PressableWithFeedback #18122

Merged

Conversation

priyeshshah11
Copy link
Contributor

@priyeshshah11 priyeshshah11 commented Apr 28, 2023

Details

The main purpose of this PR is to disable the button to prevent multiple clicks during onPress call execution.
It updates the PressableWithFeedback component to achieve this. It also updates the Button component to replace the usage of Pressable with PressableWithFeedback component to make use of this for all buttons. It also updates the createWorkspace action to return a Promise.

There are some minor changes to BaseGenericPressable component as well to fix some errors discovered during implementation.

Fixed Issues

$ #14572
PROPOSAL: #14572 (comment)

Tests

  1. Go to Settings -> Workspaces.
  2. Click on New Workspace multiple times quickly
  3. Verify only one workspace is created.
  • Verify that no errors appear in the JS console

Offline tests

Same as above

QA Steps

  1. Go to Settings -> Workspaces.
  2. Click on New Workspace multiple times quickly
  3. Verify only one workspace is created.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android / native
    • Android / Chrome
    • iOS / native
    • iOS / Safari
    • MacOS / Chrome / Safari
    • MacOS / Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that the left part of a conditional rendering a React component is a boolean and NOT a string, e.g. myBool && <MyComponent />.
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I verified the translation was requested/reviewed in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG))
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR.

Screenshots/Videos

Web
web.mov
Mobile Web - Chrome
mweb-chrome.mov
Mobile Web - Safari
mweb-safari.mov
Desktop
desktop.mov
iOS
ios.mov
Android
disable-button-android.mov

@priyeshshah11 priyeshshah11 requested a review from a team as a code owner April 28, 2023 02:27
@melvin-bot melvin-bot bot requested review from roryabraham and s77rt and removed request for a team April 28, 2023 02:27
@MelvinBot
Copy link

@roryabraham @s77rt One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@priyeshshah11 priyeshshah11 changed the title added disabling of Button during onPress call in PressableWithFeedback Disables the Button during onPress call in PressableWithFeedback Apr 28, 2023
@s77rt
Copy link
Contributor

s77rt commented Apr 28, 2023

@priyeshshah11 Thank you. I will review asap, in the meantime can you please fill the details section of the PR highlighting the main changes here? (e.g. refactor Button to use PressableWithFeedback, etc.)

@priyeshshah11
Copy link
Contributor Author

@s77rt sorry had totally missed that, updated now!

@@ -109,6 +109,9 @@ const propTypes = {

/** Id to use for this button */
nativeID: PropTypes.string,

/** accessibility label for the component */
accessibilityLabel: PropTypes.string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be required

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does that mean all Buttons will need an accessibility label? & should adding all of them be in scope of this PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well since accessibilityLabel is required on GenericPressable it would make sense to have this required on Button too. I think it would be better to split this to two PRs:

  1. Migrate from Pressable to PressableWithFeedback for Button
  2. Disable PressableWithFeedback on press

cc @roryabraham thoughts on this?

Copy link
Contributor Author

@priyeshshah11 priyeshshah11 Apr 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or given that I have already done the majority of the work for both the things above, I would prefer to move updating all usages of Button to add accessibilityLabels to a seperate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that accessibilityLabel should be required. I also agree that we should start by disabling PressableWithFeedback on press in this PR, then create a separate PR to migrate from Pressable to PressableWithFeedback for Button.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roryabraham I have already done the migration part in this PR as we didn't get a reply for ~2 weeks, so what's the plan of action here?

Comment on lines 263 to 264
(this.props.isDisabled && (this.props.success || this.props.danger)) ? styles.buttonOpacityDisabled : undefined,
(this.props.isDisabled && !this.props.danger && !this.props.success) ? styles.buttonDisabled : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we are moving those from inner style to wrapper style?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's how the new component works, it'll make sense when you try it out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please elaborate? Those styles were being passed to <OpacityView /> why we are passing them to <Pressable />

style={state => [
getCursorStyle(isDisabled, [props.accessibilityRole, props.role].includes('text')),
props.style,
StyleUtils.parseStyleFromFunction(props.style, state),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change is required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is required to apply styles based on the component states like isHovered, isDisabled, etc just like the lines below it. @robertKozik told me to add it so maybe he can add more context here if needed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea inside BaseGenericPressable was to spread styling based on Pressable state into different props (HoverStyle, focusStyle etc.). So in place of:

style={({isHovered, isPressed}) => ({
    isHovered && <<hoverStyle>>,
    isPressed && <<pressStyle>>
})}

We would get:

pressedStyle={<<pressStyle>>}
hoverStyle={<<hoverStyle>>}

But as PressableWithFeedback shifts all these state-aware styling into opacityView and passes wrapperStyle as the style prop to GenericPressable, I suggested this change in order to still have the possibility of state-aware styling inside the wrapperStyle prop.

During our convo with @priyeshshah11 I came up with this change because I thought It could be used there.
All in all, even when there is no use here, this change is beneficial.

src/pages/workspace/WorkspacesListPage.js Outdated Show resolved Hide resolved
@@ -949,7 +950,7 @@ function createWorkspace(ownerEmail = '', makeMeAdmin = false, policyName = '',
],
});

Navigation.isNavigationReady()
return Navigation.isNavigationReady()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actions should not return a promise. Just revert this. The InteractionManager seems to be enough for our case, so no promise is actually needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but didn't @roryabraham say so here ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may create a pattern for further devs which I'm not sure if we want that.

But do we actually need a promise here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we do need it, as InteractionManager is not enough. And I think we were aware of this pattern change & accepted it before assigning/commencing work on this PR as per @roryabraham's comments in Slack.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I understand that this is a new pattern. In general @s77rt is right that actions should not return a promise, but the spirit of that rule is that we should never be waiting for API requests to complete before doing something, because all our API interactions are completed optimistically.

What we need is a way to wait for the optimistic data to be written to Onyx and for subscribers to be updated before returning. But because that's async we need to use a Promise.

Copy link
Contributor

@s77rt s77rt May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually at this point (the following statement may change) we are waiting for onyx data update since the optimistic data use onyx SET method which is blocking (sync).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The promise used here does not wait for onyx. It waits for navigation, it just happen that at that point onyx data is updated.

Yes, that's a very good point. I think this is maybe fine for now, but long-term we do need a better way to wait for just the optimistic data to be written to cache and for subscribers to be updated.

optimistic data use onyx SET method which is blocking (sync).

Onyx.set is async:

We could feasibly make a sync Onyx API on iOS and Android right now using JSI, but have no way to do so on web currently. We make Onyx.set async to make it more consistent with Onyx.merge

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sorry for the confusion I was actually referring to another behaviour. See https://expensify.slack.com/archives/C01GTK53T8Q/p1682938682704609 but that's unrelated to the PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we do need it, as InteractionManager is not enough. And I think we were aware of this pattern change & accepted it before assigning/commencing work on this PR as per @roryabraham's comments in Slack.

@priyeshshah11, hi, very sorry for the interruption. We are a little confused about the Promise here. Do you have any description or links that can show us the actual effect of this Promise? If we remove it, what problems may occur? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ntdiary If this is not a promise then we don't have a simple way of knowing when this action completed & when to re-enable the button.

@s77rt
Copy link
Contributor

s77rt commented Apr 29, 2023

@priyeshshah11 Can you please fix the hover style (and press style). The button looks too much dimmed than it should

main:
Screenshot from 2023-04-29 01-36-25

PR:
Screenshot from 2023-04-29 01-37-13

@priyeshshah11
Copy link
Contributor Author

@priyeshshah11 Can you please fix the hover style (and press style). The button looks too much dimmed than it should

So earlier we were using the same dimming value for hover & press which was 0.5 & now PressableWithFeedback correctly uses the pressDimValue (0.2) when pressed & pressHoverValue (0.5) when hovered. Let me know what we want.

cc: Hi 👋 @shawnborton 😄

@s77rt
Copy link
Contributor

s77rt commented Apr 30, 2023

It looks too dim for a hover state 😅 Let's get this to reflect the staging values

Comment on lines 263 to 264
(this.props.isDisabled && (this.props.success || this.props.danger)) ? styles.buttonOpacityDisabled : undefined,
(this.props.isDisabled && !this.props.danger && !this.props.success) ? styles.buttonDisabled : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please elaborate? Those styles were being passed to <OpacityView /> why we are passing them to <Pressable />

Comment on lines 277 to 278
(this.props.success && isHovered && !isDisabled) ? styles.buttonSuccessHovered : undefined,
(this.props.danger && isHovered && !isDisabled) ? styles.buttonDangerHovered : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The style here is not correct. Previously we had const activeAndHovered = !this.props.isDisabled && hovered; where the active state was solely base on the isDisabled prop. Now it's based on the isDisabled state which is based on both isDisabled prop and isLoading prop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right for both the above comments, changes applied.

{...propsWithoutStyling}
disabled={disabled}
onPress={(e) => {
if (disabled) { return; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required? If the button is disabled how would the onPress even get called?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're correct, I had added it as for some reason I was still able to click it while initial testing but couldn't reproduce it now so have removed it.

@priyeshshah11
Copy link
Contributor Author

It looks too dim for a hover state 😅 Let's get this to reflect the staging values

happy to change it to whatever needed, but can we please get a confirmation from @robertKozik & @shawnborton first? As I believe @robertKozik added that value here

@shawnborton
Copy link
Contributor

Hmm we already have a value for the hover color right? And then when it is actively being pressed, we can use the .2 opacity dim.

@priyeshshah11
Copy link
Contributor Author

Hmm we already have a value for the hover color right? And then when it is actively being pressed, we can use the .2 opacity dim.

yup that's correct, hover has greenHover colour & a 0.5 opacity dim & press has 0.2 opacity dim.

@shawnborton
Copy link
Contributor

We should not be dimming opacity on hover, we just use a different color for that. We should just have a global dim on the pressable stuff.

@priyeshshah11
Copy link
Contributor Author

@robertKozik could you please share your thoughts on why you had added/used hoverDimmingValue on hover? & is it ok for me to remove it if the design suggests so?

@priyeshshah11
Copy link
Contributor Author

@s77rt I have made the changes as per Shawn's comments above, i.e. use hover colours when hovered (without any dim) & use 0.2 opacity dim when pressed.

Copy link
Contributor

@s77rt s77rt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a slight change

Comment on lines 288 to 296
<View>
{this.renderContent()}
{this.props.isLoading && (
<ActivityIndicator
color={(this.props.success || this.props.danger) ? themeColors.textLight : themeColors.text}
style={[styles.pAbsolute, styles.l0, styles.r0]}
/>
)}
</View>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<View>
{this.renderContent()}
{this.props.isLoading && (
<ActivityIndicator
color={(this.props.success || this.props.danger) ? themeColors.textLight : themeColors.text}
style={[styles.pAbsolute, styles.l0, styles.r0]}
/>
)}
</View>
<>
{this.renderContent()}
{this.props.isLoading && (
<ActivityIndicator
color={(this.props.success || this.props.danger) ? themeColors.textLight : themeColors.text}
style={[styles.pAbsolute, styles.l0, styles.r0]}
/>
)}
</>

@s77rt
Copy link
Contributor

s77rt commented May 1, 2023

@priyeshshah11 Thank you

@shawnborton Isn't 0.2 too dim compared to staging or this is actually intended?
Screenshot from 2023-05-01 14-57-27
Screenshot from 2023-05-01 14-57-21

@hungvu193
Copy link
Contributor

Hello guys, I'm working on an issue that need to use PressableWithFeedback and I realized when user first try to click the Submit button, the cursor will briefly change into none-select cursor for a while. Is this a feature?

Screen.Recording.2023-05-04.at.15.30.02.mov

@priyeshshah11
Copy link
Contributor Author

Hello guys, I'm working on an issue that need to use PressableWithFeedback and I realized when user first try to click the Submit button, the cursor will briefly change into none-select cursor for a while. Is this a feature?

Screen.Recording.2023-05-04.at.15.30.02.mov

That happens when a button is disabled, and we disable the button when initially clicked until the onPress is completed to avoid multiple quick clicks. But that should be only in this PR, are you using this PR in your case? could you link that issue for reference?

@hungvu193
Copy link
Contributor

That happens when a button is disabled, and we disable the button when initially clicked until the onPress is completed to avoid multiple quick clicks. But that should be only in this PR, are you using this PR in your case? could you link that issue for reference?

@priyeshshah11 I'm working on this issue (#17103), I need to replace TouchableOpacity with Pressable but still want to keep the activeOpacity behavior, so I decided to use PressableWithFeedback, it's working fine with mWeb, but with Web version, it will look like this:

Screen.Recording.2023-05-04.at.15.44.47.mov

So I have questions:

  • Should we add more props to PressableWithFeedback that allow user to interact with button multiple times in very short moment (similar with takeEvery and takeLatest in redux-saga).
  • In case this is feature, is there any way to prevent that or is there any alternative solution?
    Thank you.

@s77rt
Copy link
Contributor

s77rt commented May 5, 2023

@priyeshshah11 @roryabraham Can you please address @hungvu193's concern. Should we add an option to opt out from the "disable button on click" functionality?

@priyeshshah11
Copy link
Contributor Author

@hungvu193 I cannot answer the 1st point as that's for @roryabraham to decide but I don't see any harm in adding it if you need it. For the second point, currently there is no way to prevent this.

Either way, it doesn't need to hold up this PR.

cc: @s77rt

@roryabraham
Copy link
Contributor

@robertKozik let me know where the best place is to discuss this, but I think having elements at .2 opacity onPress is just way too dim. Can we change that to .8 please?

If @shawnborton thinks this is best, I think we can make that change without further discussion

@roryabraham
Copy link
Contributor

@roryabraham & @slafortune would it be fair to ask to increase the job price based on the increase in scope & effort than initially planned?

@priyeshshah11 the issue is the better place to ask this question than in the PR

Copy link
Contributor

@roryabraham roryabraham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting concern from @hungvu193, and not one that I considered before. I think it is a feature that:

  • When a Pressable becomes disabled, then we show the none-select cursor.
  • When a Pressable is pressed, it becomes disabled until onPress completes to prevent multiple repeated interactions.

However, when these things both happen in a very short time period, it looks janky, and that's a problem we should solve.

In order to solve that problem, I suggest a naïve workaround to only change the cursor style after a brief timeout. Something like this (haven't tested but this should illustrate the idea):

const isDisabled = useMemo(() => {
    let shouldBeDisabledByScreenReader = false;
    if (enableInScreenReaderStates === CONST.SCREEN_READER_STATES.ACTIVE) {
        shouldBeDisabledByScreenReader = !isScreenReaderActive;
    }
    
    if (enableInScreenReaderStates === CONST.SCREEN_READER_STATES.DISABLED) {
        shouldBeDisabledByScreenReader = isScreenReaderActive;
    }
    
    return props.disabled || shouldBeDisabledByScreenReader;
}, [isScreenReaderActive, enableInScreenReaderStates, props.disabled]);
  
const [shouldUseDisabledCursor, setShouldUseDisabledCursor] = useState(isDisabled);
useEffect(() => {
    if (!isDisabled) {
        setShouldUseDisabledCursor(isDisabled);
    } else {
        setTimeout(() => setShouldUseDisabledCursor(isDisabled), 1000);
    }
}, [isDisabled]);


...
...

getCursorStyle(shouldUseDisabledCursor, [props.accessibilityRole, props.role].includes('text')),

@priyeshshah11
Copy link
Contributor Author

Interesting concern from @hungvu193, and not one that I considered before. I think it is a feature that:

@roryabraham I assume you're asking for this feature to be implemented in a seperate PR as it wasn't in the scope for this PR, right?

@priyeshshah11
Copy link
Contributor Author

Interesting concern from @hungvu193, and not one that I considered before. I think it is a feature that:

@roryabraham I assume you're asking for this feature to be implemented in a seperate PR as it wasn't in the scope for this PR, right?

@roryabraham could you please confirm? or merge this PR if it's good to go?

cc: @s77rt @mananjadhav

@s77rt
Copy link
Contributor

s77rt commented May 12, 2023

I'm okay going either way. Deferring to @roryabraham

Copy link
Contributor

@roryabraham roryabraham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, we can handle this in a follow-up

@OSBotify
Copy link
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/roryabraham in version: 1.3.14-0 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@ntdiary ntdiary mentioned this pull request May 15, 2023
56 tasks
@OSBotify
Copy link
Contributor

🚀 Deployed to production by https://github.com/yuwenmemon in version: 1.3.14-14 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@@ -1078,7 +1079,7 @@ function createWorkspace(ownerEmail = '', makeMeAdmin = false, policyName = '',
},
);

Navigation.isNavigationReady().then(() => {
return Navigation.isNavigationReady().then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from #17452 (comment), this return promise is not used anywhere. So this change is unnecessary.
Can you please confirm?
cc: @roryabraham @priyeshshah11 @s77rt @mananjadhav

Copy link
Contributor

@s77rt s77rt May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops we were actually supposed to return a value on Button. We still need the promise though. Actually we may not need it. I raised this here #18122 (comment) already. Gven that this worked even that the promise was never used then it's probably not needed.

@priyeshshah11 Can you please raise a quick follow up PR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I couldn't reproduce the original issue with this PR. Still need promise though?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we don't need it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@s77rt what's the conclusion here? do you still want me to raise another PR? to remove the promise part or instead return it from the onPress? My view is that we should keep it & return it but up to you.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priyeshshah11 Raise a PR that simply return the onPress on Button. (promise may be removed on the other PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@s77rt here it is #19378

Comment on lines +44 to +46
InteractionManager.runAfterInteractions(() => {
if (!(onPress instanceof Promise)) {
setDisabled(props.disabled);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The props.disabled that we are using inside the InteractionManager.runAfterInteractions callback or the one after the promise is resolved (few lines below) is evaluated at the time we call onPress and that value is used later. Meaning props.disabled does not reflect the current state but the state that we evaluated when onPress was initially called.

To better explain, here is an example:

  1. props.disabled is false
  2. Call onPress
  3. Evaluate the promise callback, After the promise is resolved we will call setDisabled(false) // props.disabled is evaluated now
  4. Promise is not resolved yet (button action still being executed)
  5. props.disabled is set to true
  6. Promise is resolved
  7. Now we will call the previously evaluated callback which is setDisabled(false) even though the current props.disabled is true

This logic is copied from OptionRow but we missed that case in both PRs which lead to a regression #20983.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.