Unexpected state update results in "useState" function

Viewed 145

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.

1 Answers
Related