Automatic batching changes code that relied on it not happening

React 17 batched state updates inside event handlers and not inside promises, timeouts or native handlers; 18 batches everywhere.

async function save() {
  setSaving(true)
  await api.save(draft)

  setSaving(false)
  setSaved(true)
  // 17: two renders. 18: one.
}

// the code that breaks: anything reading the DOM between
// two setState calls and expecting the first to have
// been applied.

// the escape hatch, which should be rare
import { flushSync } from 'react-dom'
flushSync(() => setSaving(false))

Fewer renders is the intended benefit and the breakage is in code that had come to depend on the inconsistency — usually a measurement taken between two updates, or a third-party widget synchronised by hand. flushSync exists for those and using it more than once or twice in an application is a sign that something else is wrong.