Reputation: 43487
React state updates are asynchronous, so you shouldn't do things like this:
const _handleChange = (event) => {
setEmail(event.target.value);
_updateEmailPermissions(email);
};
Because the setter might not actually finish before the update function is called.
But what if we have something like this?
const handleSubmit = (event) => {
event.preventDefault();
_updateEmailPermissions(email);
// ...
}
return (
<form onSubmit={handleSubmit}>
<SomeComponent onChange={(event) => setEmail(event.target.value)} />
<button>Submit</button>
</form>
)
Is this considered safe? I haven't managed to break it no matter how fast I try to jump from changing SomeComponent to submitting the form, but I wonder if there's any case in which the handleSubmit
function will work with an outdated email.
If it's not safe, what's a better way of doing this considering I need email
to be usable in the form submit event? Let's assume that updating permissions is quite slow, so I'd rather not use the useEffect
fix that triggers when email
is changed to call it.
Background
My particular use case is a bit more complex, but I think it reduces to the above example: I actually have a table (AG Grid) in a child component that calls a setRows
React state setter passed as a prop to it whenever a row changes. This is fine for me because I will only ever have a few rows. The parent component then uses rows
in a form submit event like above. I need to know if the rows
it uses are always the ones actually displayed by the table or if the asynchronous nature of state setters might ever let things get out of sync between row change and form submission.
Upvotes: 1
Views: 710
Reputation: 1725
Yes, this is safe.
The update happens asynchronously, but it is still very quick. In the code example below, we are literally trying to use email
one line of code after calling setEmail
. Since setEmail
is asynchronous, this is not a great idea:
const _handleChange = (event) => {
setEmail(event.target.value);
_updateEmailPermissions(email);
};
Although setState
is asynchronous, in most use cases it will appear to happen instantly.
In your use case, the timeline will always look like setEmail
-> user input
-> _updateEmailPermissions
. In this case, the user input
step will leave ample time for the email
state to finish updating.
I've created a code sandbox example showing how quickly email
state is updated. You can see that the email
state value which is placed inside a <p>
is updated very quickly, and is always updated well before the user could possibly click Submit
. Here's the example:
You mentioned that you "need to know if the rows it uses are always the ones actually displayed by the table". You can guarantee this by keep a single source of truth for the data by using controlled components.
Consider this example:
const [email, setEmail] = useState("");
...
return (
<input
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
Since we are setting the value
of our input
to the state variable email
, we will always know that whatever value is displayed in the input field is the same as the value stored in the email
state variable.
If we erroneously omit value
like this: <input onChange={(event) => setEmail(event.target.value)} />
then we will have two sources of truth: 1. The DOM which handles the html input
element, and 2. The state variable called email
. This has potential to cause bugs later.
Upvotes: 1
Reputation: 1001
I suggest using this code
const _handleChange = (event) => {
let temp =event.target.value;
setEmail(temp);
_updateEmailPermissions(temp);
};
I think using another event is working safely, but I think you can use a promise for that.
setAsyncState = (newState) =>
new Promise((resolve) => this.setState(newState, resolve))
Upvotes: 0