I have this function in ReactJS. When user opens home page the good entries number from database is shown. But when I refresh page the counting starts from 0+1. Where is the mistake here?
const [user, setUser] = useState({ id: "", name: "", email: "", entries: 0, joined: "", });
const onButtonSubmit = () => {
setImageUrl(input);
fetch("http://localhost:3000/imageurl", {
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: "bearer " + localStorage.getItem("token"),
},
body: JSON.stringify({
input: input,
}),
})
.then((response) => response.json())
.then((response) => {
if (response) {
setUser((prev) => {
localStorage.setItem(
"user",
JSON.stringify({
...JSON.parse(localStorage.getItem("user")),
entries: prev.entries + 1,
})
);
return { ...prev, entries: prev.entries + 1 };
});
}
displayPredictions(handleApiPredictions(response));
})
.catch((err) => console.log(err));
};
const [user, setUser] = useState({ id: "", name: "", email: "", entries: 0, joined: "", });
First, your state is initialized when the page loads with entries: 0
.
Then:
JSON.stringify({
...JSON.parse(localStorage.getItem("user")),
entries: prev.entries + 1,
})
You get the item from local storage. and merge it with the previous state, the one that is initialized with entries: 0
.
So you are basically doing this.
{
...({ entries: 123 }),
entries: ({ entries: 0 }).entries + 1
}
So the second entries
that starts at 0
is overwriting the first.
To fix it, just initialize your state from local storage instead.
const [user, setUser] = useState(
localStorage.getItem('user') ??
{ id: "", name: "", email: "", entries: 0, joined: "", }
);
This falls back to the initial state if nothing is in local storage.
And now in your setter, you don't have to fetch from local storage at all.
setUser((prev) => {
const newUserState = { ...prev, entries: prev.entries + 1 }
localStorage.setItem("user", JSON.stringify(newUserState))
return newUserState
})
It's not recommend to have your state setters cause side effects directly (such as writing to local storage). What if you update this object from some other function? You'll have to duplicate your code for saving to local storage there as well.
A more correct way to do this would be to use an effect to keep local storage up to date with any changes from any source.
That might look something like this:
// Initialize state with local storage item, or fallback.
// Use a function for initial state so that fetching from
// local storage only happens once.
const [user, setUser] = useState(() =>
localStorage.getItem('user') ??
{ id: "", name: "", email: "", entries: 0, joined: "", }
);
// Writes to local storage after the `user` state is set.
useEffect(() => {
localStorage.setItem("user", JSON.stringify(user))
}, [user])
// Now when you set the state, all you need to do is:
setUser((prev) => {
return { ...prev, entries: prev.entries + 1 };
});