reactjsreact-hooksdevelopment-environmentuse-statestrict-mode

Unexpected state update results in "useState" function


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.


Solution

  • Perhaps this is due to the fact that the setState is called twice in strict mode in development.
    Setting the state value does not depend on the previous value, but on the time. This is a side effect.

    Related links
    https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects https://github.com/facebook/react/issues/12856