1

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.

Louay Al-osh
  • 3,177
  • 15
  • 28
4ae1e1
  • 7,228
  • 8
  • 44
  • 77

1 Answers1

2

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

Andrey
  • 928
  • 1
  • 12
  • 20
  • Great, so basically setting a new date in the next state is an impurity that `React` tries to catch in `strict-mode`, thank you. – Louay Al-osh Nov 10 '21 at 05:28
  • Thank you! I actually figured out it was strict mode double calling the method (planned to submit an answer myself but was too busy) but the doc link is very helpful. I already read that page but somehow missed this very important section, facepalm. It might be more helpful if instead of suppressing `console.log`, they modified it to print "From strict mode double-invoking the function: ...". – 4ae1e1 Nov 14 '21 at 09:10