1

I'm working on a RN app for my company and I've been experiencing this weird issue that isn't RN specific - more to do with useState component rerendering.

const [checkoutButtonEnabled, setCheckoutButtonEnabled] = useState<boolean>(
        true);

const handleCheckout = useCallback(async () => {
    setCheckoutButtonEnabled(false);
    
    // bunch of async code here making network requests etc

   // reenable checkout button if some requests fail (so the user can try ordering again with another payment method, for instance)
  }

return (
  // JSX
  <Button enabled={checkoutButtonEnabled} onPress={handleCheckout} />
 // JSX
);

Clicking on this button once would have the expected outcome - the button would be disabled after the checkoutButtonEnabled state value changes and the component gets rerendered. However, during testing, I found that if I click on the button multiple times consecutively, the event handler will still fire, even though the checkoutButtonEnabled value has changed. Theoretically this should mean that the button should also be disabled.

I found an SO answer (ReactJs: Prevent multiple times button press) with an attached github issue link in which Dan Abramov gave an example of how to overcome this issue by passing in the callback function in the onPress event handler conditionally. Here is the github link - https://github.com/facebook/react/issues/11171#issuecomment-357945371

Following his example, once I changed my Button component to be like this -

 <Button enabled={checkoutButtonEnabled} onPress={checkoutButtonEnabled ? handleCheckout : undefined} />

it's working as expected. I can't really find why the button doesn't get disabled after the first time I click it, but once I conditionally pass in the event handler function, it works as expected (the event handler callback is only fired once).

If the conditional check is reading the latest value of checkoutButtonEnabled, doesn't it mean that the component has already rerendered? If that's the case, then I don't really understand why the ternary operator is getting the correct updated value after just one click (expected behavior) but the button takes some time to be rerendered with the correct value of the enabled prop.

In the attached github link, this user also asked the same question that I'm asking - https://github.com/facebook/react/issues/11171#issuecomment-410135165 but he didn't get any replies.

I'd love to know why this is happening so I can prevent similar issues in the future. Thanks!

Edit:

I found something that I thought was not related but apparently it is -

const handleCheckout = useCallback(async () => {
    setCheckoutButtonEnabled(false);
    dispatch(setShowLoadingTransparent(true));

    // bunch of async code here making network requests etc

   // reenable checkout button if some requests fail (so the user can try ordering again with another payment method, for instance)
  }

After calling setState, we are also dispatching a redux action, which in turns updates the App.tsx component, which then causes a rerender of all child components - thus explaining why the Button in question can still be clicked. However, I'm still wondering why the ternary operator check in the onPress event handler works immediately. If React batches event handler calls, then my assumption is that both the setState and dispatch calls would be batched together - thus triggering only one render. If this were the case, wouldn't the event handler still pass in the handleCheckout callback instead of undefined until the component in question rerenders (as a child component of App.tsx, which is subscribed to the Redux store, thus rerendering after dispatching the aforementioned action)?

Soo Hwan Kim
  • 83
  • 1
  • 6

0 Answers0