Skip to content

In StrictMode, the useState() initializer function is called twice, but one of the results is discarded #20090

Closed
@drcmda

Description

React version: 17.0.1
React reconciler: 0.26.0

Steps To Reproduce

Link to code example: https://codesandbox.io/s/r3f-contact-shadow-forked-iggyv?file=/src/index.js:308-745

let log = console.log

let id = 0
function Obj(node) {
  this.id = id++
  log('constructor', this.id)
  node.addEventListener('wheel', () => log(this.id, 'wheel'), false)
}

function App(props) {
  // The object is created twice, why does react do that, it's not documented
  const [obj] = useState(() => log('new object') || new Obj(document.body))
  return <div>{obj.id}</div>
}

ReactDOM.unstable_createRoot(document.getElementById('root')).render(<App />)

old demo

https://codesandbox.io/s/r3f-contact-shadow-forked-e44m3?file=/src/index.js

This demo creates a local object which is supposed to live within the components lifecycle.
For some reason concurrent mode creates two versions of that object, but one is stuck in the view section.
These controls aren't allowed to zoom, yet, when you give you mousewheel - it zooms. The control clearly receives the flag.

This does not happen in blocking mode and previous reconcilers (for instance react 16.4.x and reconcilers pre 0.26

Debugging in this is almost impossible as React swallows console.logs. Some users have found out that it indeed creates two branches of local state: https://twitter.com/belinburgh/status/1319990608010874883

The state object (orbit-controls) has side-effects, it creates events, but that is and should be of no consequence.

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions