They're in a race. Normally you'd expect the state to get set first, since the state setters are operating just locally within the DOM and the fetch
has to go talk to a server, but they're in a race and you can't count on which one is going to win the race.
If you want to control which happens first, you need to do that explicitly. It's easier to make the fetch
finish first since you can just wait to call the state setters until the end of the fetch
process. That also gives you the opportunity to handle a fetch
failure (transient network error, for instance) without losing the information the user provided because you've already cleared it from state:
function onSub(e, setList, list) {
e.preventDefault();
fetch("http://localhost:3001/addData", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title: item, amt: amount }),
})
.then((res) => {
if (!res.ok) { // This check was missing
throw new Error(`HTTP error ${res.status}`);
}
return res.text();
})
.then((data) => {
console.log(data);
setList([...list, { item, amount }]);
setItem("");
setAmt("");
})
.catch(error => {
setError(/*...some error message saying something went wrong...*/);
});
}
Side note: I explain the comment about the check that was missing here. To avoid having to write all that boilerplate, I suggest having a few handy utility functions lying around, for instance:
export async function postJSON(url, data) {
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!res.ok) { // This check was missing
throw new Error(`HTTP error ${res.status}`);
}
return res;
}
Then the code would look like this:
function onSub(e, setList, list) {
e.preventDefault();
postJSON("http://localhost:3001/addData", { title: item, amt: amount })
.then((data) => {
console.log(data);
setList([...list, { item, amount }]);
setItem("");
setAmt("");
})
.catch(error => {
setError(/*...some error message saying something went wrong...*/);
});
}