Skip to content

Commit 790b524

Browse files
authored
Fix setState ignored in Safari when iframe is added to DOM in the same commit (#23111)
* Fix setState being ignored in Safari * Add a regression test * Add comment
1 parent 51947a1 commit 790b524

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
*/
9+
10+
'use strict';
11+
12+
let React;
13+
14+
let ReactDOM;
15+
let act;
16+
17+
describe('ReactDOMSafariMicrotaskBug-test', () => {
18+
let container;
19+
let simulateSafariBug;
20+
21+
beforeEach(() => {
22+
// In Safari, microtasks don't always run on clean stack.
23+
// This setup crudely approximates it.
24+
// In reality, the sync flush happens when an iframe is added to the page.
25+
// https://github.com/facebook/react/issues/22459
26+
let queue = [];
27+
window.queueMicrotask = function(cb) {
28+
queue.push(cb);
29+
};
30+
simulateSafariBug = function() {
31+
queue.forEach(cb => cb());
32+
queue = [];
33+
};
34+
35+
jest.resetModules();
36+
container = document.createElement('div');
37+
React = require('react');
38+
ReactDOM = require('react-dom');
39+
act = require('jest-react').act;
40+
41+
document.body.appendChild(container);
42+
});
43+
44+
afterEach(() => {
45+
document.body.removeChild(container);
46+
});
47+
48+
it('should be resilient to buggy queueMicrotask', async () => {
49+
let ran = false;
50+
function Foo() {
51+
const [state, setState] = React.useState(0);
52+
return (
53+
<div
54+
ref={() => {
55+
if (!ran) {
56+
ran = true;
57+
setState(1);
58+
simulateSafariBug();
59+
}
60+
}}>
61+
{state}
62+
</div>
63+
);
64+
}
65+
const root = ReactDOM.createRoot(container);
66+
await act(async () => {
67+
root.render(<Foo />);
68+
});
69+
expect(container.textContent).toBe('1');
70+
});
71+
});

Diff for: packages/react-reconciler/src/ReactFiberWorkLoop.new.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
708708
// of `act`.
709709
ReactCurrentActQueue.current.push(flushSyncCallbacks);
710710
} else {
711-
scheduleMicrotask(flushSyncCallbacks);
711+
scheduleMicrotask(() => {
712+
// In Safari, appending an iframe forces microtasks to run.
713+
// https://github.com/facebook/react/issues/22459
714+
// We don't support running callbacks in the middle of render
715+
// or commit so we need to check against that.
716+
if (executionContext === NoContext) {
717+
// It's only safe to do this conditionally because we always
718+
// check for pending work before we exit the task.
719+
flushSyncCallbacks();
720+
}
721+
});
712722
}
713723
} else {
714724
// Flush the queue in an Immediate task.

Diff for: packages/react-reconciler/src/ReactFiberWorkLoop.old.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
708708
// of `act`.
709709
ReactCurrentActQueue.current.push(flushSyncCallbacks);
710710
} else {
711-
scheduleMicrotask(flushSyncCallbacks);
711+
scheduleMicrotask(() => {
712+
// In Safari, appending an iframe forces microtasks to run.
713+
// https://github.com/facebook/react/issues/22459
714+
// We don't support running callbacks in the middle of render
715+
// or commit so we need to check against that.
716+
if (executionContext === NoContext) {
717+
// It's only safe to do this conditionally because we always
718+
// check for pending work before we exit the task.
719+
flushSyncCallbacks();
720+
}
721+
});
712722
}
713723
} else {
714724
// Flush the queue in an Immediate task.

0 commit comments

Comments
 (0)