Reputation: 3756
In the code below, the form is not updated correctly unless I pass in previous state to the state setting call ('setForm').
To see it in action, run the code as-is, and the console will print 'true', indicating it worked. That's using 'validate1' function. If you replace that with 'validate2' (which doesn't use previous state), it fails to print 'true'.
It seems like in validate2, the second setForm call overwrites the form.long state of the first setForm call, due to the async nature of these calls. But why does validate1 work? Why does using previous state cause it to work? Any documentation on this behavior would be really helpful.
(I can make validate2 work by having one setForm that sets both fields, but the code below is contrived to show a difference in the two ways setForm can be called, with and without previous state.)
const {useState, useEffect} = React;
function LoginForm() {
const [form, setForm] = useState({
username: "",
password: "",
long: null
});
useEffect(() => {
console.log(form.long);
}, [form.long]);
const validate1 = (e) => {
e.preventDefault();
setForm((prevState) => ({
...prevState,
long: form.password.length >=3 ? true : false
}));
setForm((prevState) => ({
...prevState,
username: "*****"
}));
};
const validate2 = (e) => {
e.preventDefault();
setForm({
...form,
long: form.password.length >=3 ? true : false
});
setForm({
...form,
username: "*****"
});
};
const updateField = (e) => {
setForm({
...form,
[e.target.name]: e.target.value
});
};
return (
<form onSubmit={validate1}>
<label>
Username:
<input value={form.username} name="username" onChange={updateField} />
</label>
<br />
<label>
Password:
<input
value={form.password}
name="password"
type="password"
onChange={updateField}
/>
</label>
<br />
<button>Submit</button>
</form>
);
}
// Render it
ReactDOM.render(
<LoginForm/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Upvotes: 1
Views: 9276
Reputation: 3756
In the React documentation, they mention the following,
If the next state depends on the current state, we recommend using the updater function form, instead:
this.setState((state) => {
return {quantity: state.quantity + 1};
});
That's the reason validate1 works, because the second call to setForm depends on the previous setForm call state, and the state will be up to date if the updater function form is used.
Upvotes: 5
Reputation: 22926
Why does validate1 work though? Why does using previous state cause it to work?
setForm({
...form,
long: form.password.length >=3 ? true : false
});
setForm({
...form,
username: "*****"
});
The value of form
in the second setForm
is still the old value until the next re-render. It doesn't reflect the updated form
from the previous setForm
.
React setState
and useState
does not make changes directly to the state object.
setState
and useState
create queues for React core to update the state object of a React component.
So the process to update React state is asynchronous for performance reasons. That’s why changes don’t feel immediate.
https://linguinecode.com/post/why-react-setstate-usestate-does-not-update-immediately
Upvotes: 1