React Native Button component not being disabled immediately - javascript

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)?

Related

React - What's the difference between {doThisFunc} , {doThisFunc()} and {()=>doThisFunc()} as onClick parameter?

I'm completely new to react and kind of mediocre in JS. Just want to know what's the difference between giving a button these parameters for its onClick event :
onClick={doThisFunc}
onClick={doThisFunc()}
onClick={() => doThisFunc()}
Recently I just got a bug where I call a function with parameter like onClick={doThisFunc(a)} and the application went "Too many re-renders. React limits the number of renders to prevent an infinite loop". But then I changed it to onClick={() => doThisFunc(a)} and It works perfectly.
Sorry for such beginner question and many Thanks for your feedback!
onClick={doThisFunc}
This means "pass the function into the onclick prop". Later, when the click happens, react will call doThisFunc, passing in the click event. This is usually the right thing to do, assuming you want the function to receive the click event.
onClick={doThisFunc()}
This means "call the function immediately, and pass its return value into the onclick prop". This is usually a mistake. You would only do this if doThisFunc was a factory that creates click-handler functions, which is rare.
onClick={() => doThisFunc()}
This means "create a new function with the text () => doThisFunc(), and pass it into onClick". Later, when the click happens, react will call your new function, which will in turn call doThisFunc.
This approach is sometimes just a more verbose way of getting the same end result as option 1. But it does have the benefit that you can pass whatever values you want into doThisFunc, so if you want something other than the event to be passed in, you would use this.

Is it possible to tell exactly what is causing the render function to execute?

I have an app and when I click on a navigation bar it is causing a completely un-related component to render.
I looked at the code and can not find the connection.
It does not break anything but I find it bizarre.
I am aware of React lifecycles and was wondering if how I can troubleshoot further to see what is causing it to render().
I noticed that componentDidUpdate() is called but I don't know why it is being called by React or what is causing it to update. How can I troubleshoot further?
Maybe relevant code is, but maybe not.
componentDidUpdate(prevProps) {
console.log('DEBUG: componentDidUpdate() called', prevProps.Modal);
// Set the state of the form to the data returned from the server.
// This will allow us to PUT / Update the data as this is a controlled form.
// That is the state holds the form input.
// Typical usage (don't forget to compare props) or infinite loop will ocur.
if (this.props.Modal.data !== prevProps.Modal.data) {
// becasue the form did not update but populates we must call update manually for both URLs
this.url.updateURL(this.props.Modal.data.link);
this.img_url.updateURL(this.props.Modal.data.image);
this.setState(this.props.Modal.data);
}
}
prevProps is always the same for each call. i.e. the props do not change.
It is only mounted once and props and state do not change but it keeps updating!
See image:

What is the difference between using setItem to set multiple state values and using useEffect to do so?

I have a reset button in my app that resets a few variables of my functional component:
const [selectedItem, setSelectedItem] = useState(0);
const [a, setA] = useState('a');
const [b, setB] = useState('blue');
<button onClick={e => ???}>clicky</button>
<button onClick={e => ???}>clicky</button>
There are two ways I could 'reset' the data: monitoring selectedItem for changes using useEffect, or have a handler that does so:
<button onClick ={e => setSelectedItem(e.target.value)} />
useEffect(() => {
setA(Math.random())
setB(Math.random())
}, [selectedItem])
or
<button onClick ={e => handler(e.target.value)} />
const handler = item => {
setSelectedItem(Math.random())
setA(Math.random())
setB(Math.random())
}
What are the practical differences between these approaches? The hooks docs say to use useEffect for performing side effects, but I can't see why this approach wouldn't work as well.
What is the difference between these approaches?
I will try to answer this question in three points.
Mental model
You need to "think in effects". the UseEffect hook lets you perform side-effects that manly need to happen async like (fetch Data from API, manipulate the DOM).
based on that it's better to use UseEffect to handle side-effects so you are not confusing your colleges.
Async
You need to keep in your mind that useEffect is an async function but your event handler is sync function. That can lead to totally different behavior maybe you are not seeing a weird behavior here but maybe in other examples, you will start to notice that.
React mechanism
the last difference to notice it you need to understand React update state mechanism, react makes patches to update the state. That means in your event handler the three-state will cause one re-render because they will happen at the same time. In your useEffect that is not the case, you are updating one of them that case re-render then you are performing the effect that will case new re-render.
Maybe there are other differences but that what can I see right now.
I hope it’s a useful answer.
There are a few peculiar differences between the above two methods.
In the first method of using useEffect, you would be updating states a and b whenever selectedItem changes, be it by a button click or some other sideeffect such as a prop change. However in the second case, states a and b would only be updated if selectedItem is updated on button click and you would need to call setA and setB to update states everywhere you update selectedItem separately
Secondly, when you are using a useEffect to update state, the state update will happen after updating selectedItem, however in the second case state updates doesn't gurantee that selectedItem is updated before setting the other states and hence if the other state updated depend on selectedItem value, you need to pass the updated selectedItem value to the other state updaters separately
In short, making use of useEffect is better when you know you have to take other actions whenever a state change occurs no matter how it occurs. Also its useful when you want to take action after a particular state is updated.
I believe it's important to consider the semantics of what you're doing. For example:
<button onClick ={e => handler(e.target.value)} />
const handler = item => {
setSelectedItem(Math.random())
setA(Math.random())
setB(Math.random())
}
this means that whenever you click the button you want the 3 state variables to be changed.
On the other hand:
<button onClick ={e => setSelectedItem(e.target.value)} />
useEffect(() => {
setA(Math.random())
setB(Math.random())
}, [selectedItem])
this means whenever you click the button you want that one state variable to change and independently of that you want, whenever that one state variable changes, to change those other two state variables.
The real question you should be asking is what is it you really want to express with your code, given that it has the same end result. In short, what makes semantic sense to you? Does it make sense to say "this button can be used to change those 3 state variables" or does it make more sense to say "this button can be used to chanage the selectedItem state variable and this entire component will change the a and b state variables whenever the selectedItem changes?
It is usually important to make sure your code makes semantic sense so you don't land in the pitfalls of getting unintended side-effects when you make code changes. For example, if selectedItem ends up being changeable by other means, the 2nd method will ensure that a and b change at the same time. Do you really want that?
There's also a practical consideration. There's the eslint rule called react/no-did-update-set-state which states:
Updating the state after a component update will trigger a second render() call and can lead to property/layout thrashing.
Layout thrashing basically means there's multiple potential redraws of the layout before a user can interact with it again. In the case of useEffect this can be an issue because useEffect is triggered after a layout update and setting the state might trigger another one. It usually has no noticeable effect on very simple operations but if you have a complex component hierarchy and end up re-rendering large portions of it then you will end up with a less responsive layout.
There's also the additional consideration that with the useEffect you also need to be mindful to avoid cyclical dependency changes e.g. selectedItem changes a and a changes selectedItem or makes a change which ends up changing selectedItem somewhere further down the line.
So overall there are three notes:
Use whichever makes more semantic sense for your component
Be aware of potential layout thrashing
If useEffect does make more sense take a step back and really think about why it makes more sense and whether there is a better way to solve your problem and at the same time avoid using useEffect to set state variables.
Ok the scenario you are referring to here is not really a side-effect, or I would say the side-effect react refers to. React refers to side-effect like if you are doing a network request
In your case if you just want to reset some variables I think having a clickHandler is the way to do, you would use a useEffect like if you want to do a network request when the component loads or some props change
Hope it clarifies

How to show Toast only once after received error message prop from Redux store

I have working React-Native + Redux registration flow:
Fill inputs
Validate them in component (Formik)
Call action to store registerUser(formData)
Wait for saga to do async call to API
On API error call reducer REGISTER_ERROR what sets store variable formError to some message.
I have my component with mapped state to props (this error message is hooked to prop).
I am doing componentDidUpdate() and when the error prop from store is changed I fire ToastAndroid.show(errorMessage).
But my Toast is called multiple times, because componentDidUpdate is also called multiple times (Redux updating component multiple times).
I know quick workaround by for example creating local state visible and when this state variable is true then no other Toasts are shown.
Is there any better more common way to do it? It is pretty weird in my opinion to rely on Toast's onClose event to set the state variable to false.
As stated in the first answer it'd be good to see the actual code, but what you should be doing is only showing the toast message when the prop changes like this (assuming it's a boolean)
componentDidUpdate(prevProps) {
if(prevProps.showToast === false && this.props.showToast === true){
showToast();
}
}
Without the code, it's a bit hard to try coming up with a solution but I think I know at a high-level what you are trying to do.
If I were you, I would make the presentational component (toast UI in this context) just react to the store props/observables instead of calling the ToastAndroid.show() method directly in the life cycle method.
In terms of architecture pattern, I find this pattern works well with react applications. Hope this helps. https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

Redux - Dispatching Actions Correctly

In my app, I had small action creator functions which returns an action for a small section of the app. My rational being that I wanted to be as 'precise & specific' with my logic, yet keeping the code reusable.
To give an example, I have React 'tab' components which are reused on everywhere. In one scenario, clicking the 'tab' would -1- Make the tab active, -2- do something else that is required. In someother scenario, clicking the 'tab' would -1- Make the tab active, -2- do something else, possibly different to the first scenario above.
The logic I thought of was that I would dispatch two action creators in each scenario above. The first is obviously the common one, the second one is unique to each scenario.
// rough code for illustrative purposes
activateTab : () => {
dispatch( actionCreator1 );
dispatch( actionCreator2 );
},
render: () => {
<Tab onclick = { activateTab }
}
PROBLEM(?):
I occured to me that each action creator being dispatched would call reducer functions & then make React run its 'differing' algorithm. I.e. In the above Tab, React is recalculating all the DOM changes twice? Is this correct? Can someone please confirm?
And how should these scenarios be handled?
Should I be making an action type (& thereby action object) unique to each scenario? Which would mean, there should be only one dispatch function.
Thanks,
Should I be making an action type (& thereby action object) unique to each scenario?
Presumably you are already doing this, if you are dispatching two different actions correct? I think you do not need the SET_TAB action, if you never use it on its own, the only thing you need is to listen to more actions in your reducer.
In any case, your assumption is correct, and it's totally normal to have one reducer listen to many different actions. Consider your active tab reducer to look something like:
let initial = {
fooActiveTab: 0, // first foo tab is open
barActiveTab: 2, // third bar tab is open
}
function activeTab (state, action) {
switch (action.type) {
case 'SOMETHING_RELEVANT_TO_FOO':
case 'FOO_AJAX_SUCCESS':
return {
...state,
fooActiveTab: action.payload // set current tab
}
default:
return state
}
}
This makes your reducer composition very clear, as the tabs state listens to a variety of things that may change what the active tab is. This way you do not need to dispatch two actions on every function call.. just one is enough as long as you send along the id of the tab you want to be active.
I think you should do the above, but not to prevent React from running the diff algo.. but to be more organized. React's diff / patch will run A LOT and your app will still be very fast. Any slowness you perceive is due to components actually updating because the DOM needs to change, and not because of reconciliation.

Categories

Resources