IVlad
IVlad

Reputation: 43487

Can I rely on React state being updated in a form onSubmit handle?

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

Answers (2)

Andrew Hulterstrom
Andrew Hulterstrom

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: Edit elastic-gagarin-orjie1

Controlled Components

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

Arash Ghazi
Arash Ghazi

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

Related Questions