Consider this trivial example where a function is used to update a useState
object:
App.tsx
import { useState } from 'react';
export default function App() {
const [state, setState] = useState({
id: 0,
});
const update = () => {
setState(prev => {
const id = Date.now();
console.log('previous id:', prev.id, 'new id:', id);
return {
id,
};
});
};
return (
<div>
<div>state: {JSON.stringify(state)}</div>
<button onClick={update}>Update</button>
</div>
);
}
Put this in a standard create-react-app typescript template (with React 17.0.2), and run the app in dev mode. Keep clicking the update button and observe the console. Soon enough one would run into a discrepancy between the intended state and the actual state, e.g.
previous id: 1636301326090 new id: 1636301326260
previous id: 1636301326260 new id: 1636301326440
previous id: 1636301326440 new id: *1636301326611*
previous id: *1636301326612* new id: 1636301326804 // What???
previous id: 1636301326804 new id: 1636301326997
In the console log above, the id: 1636301326612
state came out of nowhere, as the state being set was id: 1636301326611
.
Stranger still, I cannot reproduce this when the app is built for production. Also, if I forgo functional update and just pass an object to setState
as in the commented-out code (infeasible in my actual non-toy code due to atomicity requirement), the issue also doesn't seem to be present.
I'm at a complete loss. Am I using setState
wrong somehow?
I can provide a complete example repo if necessary.