<!--
  Please provide a clear and concise description of what the bug is. Include
  screenshots if needed. Please test using the latest version of the relevant
  React packages to make sure your issue has not already been fixed.
-->

React version: 18.0.0-alpha-9175f4d15-20210928

## Steps To Reproduce

Minimal reproduction in [this codesandbox](https://codesandbox.io/s/black-moon-ymw10?file=/src/index.js).

This issue only appears in Safari, including mobile Safari. It works fine in Chrome and Firefox.

In order to reproduce, you'll need to:

0. Render the app with `createRoot` in the latest 18.0 alpha
1. Within an element's `ref` function, append an iframe to that element using `appendChild`

*The following steps have been removed after simplifying the reproduction:*

~~1. Make a state change originating in a message from a YouTube embed iframe. I assume the same issue would occur with messages from other cross domain iframes, although I have not tested this. The issue does *not* appear when making an async state change through `setTimeout`.~~
~~2. Render a tooltip using the "tippy.js" library. I haven't dug into what inside of Tippy is causing this, but commenting out the tippy element resolves the issue. I have confirmed that rendering a simple portal does *not* reproduce the issue.~~


Link to code example:

https://codesandbox.io/s/black-moon-ymw10?file=/src/index.js

## The current behavior

In the example in safari, the updater function passed to `setState` is run (confirmed with the "run state updater" console.log)... but the new state is never passed to the component function, leaving the app hanging with no error message, and displaying "not done".

## The expected behavior

In Chrome and Firefox, the App component updates after the state updater function is called, as expected, displaying "done".

---

Thanks for all the wonderful work on React, btw! It's been a lot of fun playing with the new concurrent React features. Can't wait to see them hit a stable release!

lanes for the entire render phase.
let workInProgressRootIncludedLanes: Lanes = NoLanes;
 The work left over by components that were visited during  Only
 includes unprocessed updates, not work in bailed out children.
let workInProgressRootSkippedLanes: Lanes = NoLanes;
 Lanes that were updated (in an interleaved event) during 
let workInProgressRootInterleavedUpdatedLanes: Lanes = NoLanes;
 Lanes that were updated during the render phase (*not* an interleaved event).
let workInProgressRootRenderPhaseUpdatedLanes: Lanes = NoLanes;
 Lanes that were pinged (in an interleaved event) during 
let workInProgressRootPingedLanes: Lanes = NoLanes;
 The most recent time we committed a fallback. This lets us ensure a train
 model where we don't commit new loading states in too quick succession.
let globalMostRecentFallbackTime: number = 0;
const FALLBACK_THROTTLE_MS: number = 500;
 The absolute time for when we should start giving up on rendering
 more and prefer CPU suspense heuristics instead.
let workInProgressRootRenderTargetTime: number = Infinity;
 How long a render is supposed to take before we start following CPU
 suspense heuristics and opt out of rendering more content.
const RENDER_TIMEOUT_MS = 500;
function resetRenderTimer() {
  workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS;
}
export function getRenderTargetTime(): number {
  return workInProgressRootRenderTargetTime;
}
let hasUncaughtError = false;
let firstUncaughtError = null;
let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;