Weird behavior on event handlers in React - reactjs

I wonder if someone can explain the reason of this behavior:
If on a onChange event from an <input> element I have set to point to this method:
private PasswordChanged = (event: any) => {
this.setState((prevState: IWifiState, props: IWifiProps) => {
prevState.Password = event.target.value;
return prevState;
});
}
This throw me the following error:
Where line 27 is precisely the call to event.target.value on the pasted code.
If I change to code to be like that:
private PasswordChanged = (event: any) => {
const password = event.target.value;
this.setState((prevState: IWifiState, props: IWifiProps) => {
prevState.Password = password;
return prevState;
});
}
It just works as expected... Anyone can explain why?
Thanks!

React does something called Event Pooling.
What this essentially means is that, for performance considerations, they re-use events.
At the time when you call setState, internally the object might not be okay to re-use as it might behave in ways you wouldn't expect it to (properties get nulled out once the event has served it's purpose).
It is best to save off the reference in a variable for the value that you need, as you did, and use that instead.
Basically, you are accessing it asynchronously (inside the setState function) and it is advised against doing so.
There is a workaround, but I would also advise against it.
If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.

Related

Is this correct way to render according to the state data using async await?

I got multiple buttons that render different modals.
The modals may render different results according to the data provided in the state.
I got 3 state brackets I need to consider
const [cartChangeStoreShow, setCartChangeStoreShow] = useState(false);
const [orderLineId, setOrderLineId] = useState('');
const [storeId, setStoreId] = useState('');
cartChangeStoreShow is for controlling the visible state of the modal
What I want to do is I wanna change OrderLineId and storeId before rendering the component.
The data will change according to the orderlineId and storeId.
The component is like this
<CartChangeStorePopUp
visibility={cartChangeStoreShow}
setCartChangeStoreShow={setCartChangeStoreShow}
orderLineId={orderLineId}
storeId={storeId}
/>
I am calling api inside CartChangeStorePopUp component according to prop data.
So I am handing the user press button like this.
<TouchableOpacity
onPress={() => renderCartChangeStore(cartItem)}>
<Text>
Change Store
</Text>
</TouchableOpacity>
const renderCartChangeStore = async cartItem => {
try {
await setOrderLineId(cartItem.orderLineId);
await setStoreId(cartItem.storeId);
} catch (err) {
console.log(err);
} finally {
setCartChangeStoreShow(true);
}
};
the code is working now but from what I read before
Async Await doesn't work properly with setState,So I wanna know if there is potential error with the code written here
To me, it does not make sense both the async/await presence and the try/catch/finally.
Async/await is useful when the function you're calling is dealing with something like I/O, time-consuming, where you cannot do anything than "wait" for the completion. Since "to wait" might be something not desirable in a UI context, the async/await pattern helps you to keep track to the "slow function", but even leave the CPU free to serve other useful tasks.
That being said, the "setXXX" functions of React.useState are not time-consuming: no I/O or similar task involves. Hence, the async/await is not applicable.
Going further, the "setXXX" functions of React.useState throw no error on setting. They're much like setting a variable like so:
var storeId = "";
function setStoreId(value) {
storeId = value;
}
That is, the try/catch/finally is quite useless.
If you want, you might optimize the code by grouping the three variables as a single immutable object. However, that's up to your real code.
const [storeState, setStoreState] = useState({
cartChangeStoreShow: false,
storeId: "",
orderLineId: ""
});
const renderCartChangeStore = cartItem => {
setStoreState({
cartChangeStoreShow: true,
storeId: cartItem.storeId,
orderLineId: cartItem.orderLineId,
});
};
Here is a more compact way to achieve the same behavior:
const renderCartChangeStore = cartItem => {
setStoreState({
cartChangeStoreShow: true,
...cartItem,
});
};
Bear in mind that is very important that you treat the storeState as immutable. That is, never ever change a field of the object, rather create a brand new object with the new field value.
At that point, the component should be called like so:
const handleCartChangeStoreShow = value => {
setStoreState({
...storeState,
cartChangeStoreShow: value,
});
}
<CartChangeStorePopUp
visibility={storeState.cartChangeStoreShow}
setCartChangeStoreShow={handleCartChangeStoreShow}
orderLineId={storeState.orderLineId}
storeId={storeState.storeId}
/>
Notice the handler to correctly alter the storeState object. Worthwhile mention how the new value is set. First, all the current storeState is copied to a fresh new object, then the new show value is also copied on the same object. However, since that happens after, it'll have an override-effect.

React with typescript, button function doesn't work

I'm slowly starting to learn TS and implement it to current project however I stuck and don't really understand what is wrong. Basically I have button which has dataset "mode". After clicking on button I launch confirm bar (confirm bar is not TSX yet)
<Button
height={50}
data-mode="MEMORY"
onClick={(e: React.MouseEvent<HTMLButtonElement>) =>
ConfirmBar('You sure?', supportCommands, e)
}
>
Format
</Button>
const ConfirmBar = (message, action, parameter) =>
confirmAlert({
customUI: ({ onClose }) => {
return (
<ConfirmContainer>
<Header main>{message}</Header>
<ConfirmationButton
confirm
onClick={() => {
action(parameter);
onClose();
}}
>
Yes
</ConfirmationButton>
<ConfirmationButton onClick={onClose}>No</ConfirmationButton>
</ConfirmContainer>
);
},
});
In case of yes I wish to launch function to proceed request, it worked correctly before typescript but now it throws error. I wish to get access to dataset attribute and would be glad if you guys help me and explain me why it doesn't want to work now after added typescript
const supportCommands = (el: React.MouseEvent<HTMLButtonElement>) => {
// Tried already to use el.persist(), with target and currentTarget; here is example with attempting to assign value to variable but also doesn't work.
const target = el.currentTarget as HTMLButtonElement;
let elVal = target.getAttribute('data-mode');
console.log(elVal, 'ELL');
};
And that's the error I occur:
Warning: This synthetic event is reused for performance reasons. If
you're seeing this, you're accessing the method currentTarget on a
released/nullified synthetic event. This is a no-op function. If you
must keep the original synthetic event around, use event.persist().
See fb.me/react-event-pooling for more information.
I understand that React has own system of SynthesisEvents but I thought they cause problems during asynchronous requests like with timers etc, in this situation I see no reason why it makes problem
EDIT: I made it work by adding to button e.currentTarget, and then in function just did el.dataset, now just trying to figure out what kind of type is that
This waring is because you are reusing Event object.
You passed it here ConfirmBar('You sure?', supportCommands, e)
And you reused it here action(parameter);
I don't know what do you need from paramenter but I guess it could be like this:
onClick={(e) => {
action(e);
onClose();
}}
I have never needed to use event of onClick. The only idea I can imagine is for preventDefault or stopPropagation

In this React based form, I am confused by what's going on with `setValues`. Can you help elaborate?

First of all, we have a small react based form with three fields (first name, last name, email) followed with a "register" button. Now, the author is using the following code to organise the state.
const [values, setValues] = useState({
firstName: '',
lastName: '',
email: '',
});
And then the author uses the following code to capture typed in values from the field onChange. However, I am not able to fully comprehend what is going on. I would love for someone to help me explain this.
const handleFirstNameInputChange = (event) => {
event.persist();
setValues((values) => ({
...values,
firstName: event.target.value,
}));
};
I am especially perplexed by what's going on with setValues here. For instance, why are we trying to "spread" the values in this scenario? Why is the firstName followed after the spread? Why is the anonymous function body wrapped with parentheses and braces?
Much appreciated.
setValues is the setter from your React state. It sets your email, first name, and password.
It should be more like this.
const handleChange = (event) => {
event.persist();
// the name here would be the key of your state object.
// i.e email, password and firstname
// it should be defined as the name in your input field.
const name = event.target.name;
const value = event.target.value;
setValues((values) => ({
...values,
[name]: value,
}));
};
You could use your fields like
// the name "email" has to be passed through here.
// you could access both the value and the key in your `handleChange` handler
<input type="email" name="email" onChange={handleChange} value={values.email} />
You could check the working example here
React can set state synchronously or asynchronously. This means that your state doesn't necessarily change immediately when you call setValues(). Instead, your state may update a few milliseconds later. It is up to the inner-workings of React to decide when to update your state.
As a result, React allows us to use callbacks when setting the state. The callback will give you access to the previous state which you can then use in your updated state by returning from the callback. With that in mind in your setValues() method:
setValues((values) => ({
...values,
firstName: event.target.value,
}));
Above values is your previous state - so an object. When you spread an object into another object, you're grabbing all the enumerable (own) keys from the values object and adding them to the new object you're spreading into. You can think of this as merging the properties from one object into a new object:
const a = {
aKey: 1,
bKey: 1.5
}
const b = {
...a,
bKey: 2
}
/*
b is interpreted as:
{
akey: 1,
bKey: 1.5,
bKey: 2 <-- Objects can't have duplicate keys, so this one overwrites the above
}
... which then evaluates to:
{
"aKey": 1,
"bKey": 2
}
*/
console.log(b);
Your callback then returns the newly updated object (as this is an arrow function it is an implicit return). Note that the object is wrapped in parenthesis ( ). This is because without the parenthesis the { } for the object literal would be interpreted as a code-block (so the code would be interpreted as a function-body) and not an object literal.
// Arrow function with a body (ie: code-block)
// code-block --- \/
const foo = () => {
};
// Arrow function with implict return
const bar = () => ({ // <--- object
});
A note on event.persist().
React uses SyntheticEvents. A SyntheticEvent is a wrapper to the native event object which gets passed to your event handler. React uses this wrapper for cross-browser compatibility. However, SyntheticEvents are a little different to native events as they're "pooled". Pooling essentially means that the SyntheticEvents are reused, meaning that the same object reference of event can be used for other events throughout your application. Because of this, the object needs to be "nullified" after it's done being used. "nullifying" the object means to make the values of the keys of the object null once the event-handler is finished. So the keys of the event object remain, however, the values of the keys are set to null. This way, when another event triggers, React can grab the SythenticEvent and populate its values. React uses this concept of pooling as creating a new SyntheticEvent instance for each event can be costly, thus, using a reference is more efficient as a new instance doesn't need to be created for each event.
The reason why react nullifies the event is because of performance reasons. When the values are set to null, the old values can be garbage collected which will free-up the application's memory.
So, the properties of event will be nullified once the event-handler has finished executing. As noted, setValues() may be asynchronous, meaning that it may run after your event handler has finished executing and nullifying the event object. As a result, performing event.target.value would result in an error due to the nullification of the event. So, to stop this error from occurring, we can stop the event from being pooled and thus nullified by using event.persist(). Now the event won't be reused and so there is no need for it to be nullified. As a result, another synthetic event will be added to the pool to replace the old one. For your case, persisting the entire event object is a little over-kill since all you want to persist is the event.target.value. This can be done by saving the value of event.target.value in a variable.
const firstName = event.target.value;
When the object is nullified, this won't impact our firstName variable as it is storing a value and not a reference to the value within the object. This way, when you set the state, your callback can close over this variable as use it just fine:
const firstName = event.target.value;
setValues((values) => ({
...values,
firstName
}));
Event pooling isn't something that you'll need to worry about for too much longer as it will no longer be used in React 17 (as it seems that it doesn't improve performance in modern browsers).

Using this.setState is changing my event param values

I'm working with react typescript component, and what I've noticed is that when I'm using this.setState, then it is is changing my event param values. The case is that I'm using a combobox which is calling on an event called handleChange.
<ComboBox
change={this.handleChange}
value={"test"}
/>
Under is the handler:
handleChange = (e) => {
$.get('/webapi/getItems?ID=' + e.target.value.id, (data) => {
this.setState({ textEditorValue: data });
});
}
It seems like the onChange function is being ran two times.
Because first the event param in the function has the correct values, then suddenly it changes to the basic object for events, also it runs two times the setState / the function.
How can I fix this?
There is something strange in your code, you seems want to fetch something with you api, but you do nothing with the result aka data.

Several methods for setState instead of single one

I'm developing a React app without Redux or any other state manager.
Let's say I want to do three things when a button is clicked:
Enable some other button
Remove a label
Show a confirmation toaster
These 3 things are controlled by 3 variables of the state. I could therefore do simply this:
myHandler = () => {
this.setState({
canSave: true, // Enable the button
isLabelVisible: false, // Hide label
isConfirmationMessageVisible: true, // Show confirmation message
});
}
However, I could get rid of those comments by using some private class functions, like this:
myHandler = () => {
this.toggleSaveButton(true);
this.toggleLabel(false);
this.toggleConfirmationMessage(true);
}
toggleSaveButton= (enabled) => {
this.setState({
canSave: enabled,
});
}
toggleLabel= (visible) => {
this.setState({
isLabelVisible: visible,
});
}
toggleConfirmationMessage= (visible) => {
this.setState({
isConfirmationMessageVisible: visible,
});
}
In addition to remove those comments which could easily get out-of-sync with the code, this allows me to reuse the private methods in other places of my code.
Since this is handled in a synthetic event, I have read here that it will be batched, so I can expect no performance penalty.
My question is: is this good practice? have you used this approach? can you point some potential drawbacks I can not foresee right now?
This is perfectly fine. As you mention, React batches all updates to the state that are triggered from event handlers. This means that you can safely use multiple setState() like you are doing here.
In current release, they will be batched together if you are inside a React event handler. React batches all setStates done during a React event handler, and applies them just before exiting its own browser event handler.
The only thing you need to look out for is if you are changing the same state twice from two setState() calls. For example:
a() {
this.setState({foo: foo+1});
}
b() {
this.setState({foo: foo+1});
}
Calling a() and then b() from the same event, will increment foo by 1, not two.
Instead use:
a() {
this.setState(prevState => ({foo: foo+1}));
}
b() {
this.setState(prevState => ({foo: foo+1}));
}
this will correctly increment foo by 2.
For potential future readers who are not calling multiple setState() from an event handler, I should note the following:
With current version, several setStates outside of event handlers (e.g. in network responses) will not be batched. So you would get two re-renders in that case.
Alternative solution
What you can do though, regardless if you call setState() from an event handler or not, is to build an object and then set it as the new state. The potential benefit here is that you only set the state once and thus don't rely on batching or where the function was triggered from (event handler or not).
So something like:
myHandler = () => {
let obj = {}
obj = this.toggleSaveButton(obj, true);
obj = this.toggleLabel(obj, false);
obj = this.toggleConfirmationMessage(obj, true);
this.setState(obj);
}
toggleSaveButton= (obj, enabled) => {
obj.canSave = enabled;
return obj;
}
toggleLabel= (visible) => {
obj.isLabelVisible = visible;
return obj;
}
toggleConfirmationMessage= (visible) => {
obj.isConfirmationMessageVisible = visible;
return obj;
}

Resources