@samitouri / QOS-React-2 / commits / bb89b4eacc

Bail out of updates in offscreen trees (#15666)

* Bail out of updates in offscreen trees * Address review

Dan Abramov committed May 16, 2019 at 11:12 UTC bb89b4eacc4edc0954910955322296789d8e2089
3 files changed +83 -2
packages/react-dom/src/__tests__/ReactUpdates-test.js
+72
@@ -1290,6 +1290,78 @@ describe('ReactUpdates', () => {
1290 expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1291 });
1292
1293 + it('delays sync updates inside hidden subtrees in Concurrent Mode', () => {
1294 + const container = document.createElement('div');
1295 +
1296 + function Baz() {
1297 + Scheduler.yieldValue('Baz');
1298 + return <p>baz</p>;
1299 + }
1300 +
1301 + let setCounter;
1302 + function Bar() {
1303 + const [counter, _setCounter] = React.useState(0);
1304 + setCounter = _setCounter;
1305 + Scheduler.yieldValue('Bar');
1306 + return <p>bar {counter}</p>;
1307 + }
1308 +
1309 + function Foo() {
1310 + Scheduler.yieldValue('Foo');
1311 + React.useEffect(() => {
1312 + Scheduler.yieldValue('Foo#effect');
1313 + });
1314 + return (
1315 + <div>
1316 + <div hidden={true}>
1317 + <Bar />
1318 + </div>
1319 + <Baz />
1320 + </div>
1321 + );
1322 + }
1323 +
1324 + const root = ReactDOM.unstable_createRoot(container);
1325 + root.render(<Foo />);
1326 + if (__DEV__) {
1327 + expect(Scheduler).toFlushAndYieldThrough([
1328 + 'Foo',
1329 + 'Foo',
1330 + 'Baz',
1331 + 'Foo#effect',
1332 + ]);
1333 + } else {
1334 + expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Baz', 'Foo#effect']);
1335 + }
1336 +
1337 + const hiddenDiv = container.firstChild.firstChild;
1338 + expect(hiddenDiv.hidden).toBe(true);
1339 + expect(hiddenDiv.innerHTML).toBe('');
1340 +
1341 + // Run offscreen update
1342 + if (__DEV__) {
1343 + expect(Scheduler).toFlushAndYield(['Bar', 'Bar']);
1344 + } else {
1345 + expect(Scheduler).toFlushAndYield(['Bar']);
1346 + }
1347 + expect(hiddenDiv.hidden).toBe(true);
1348 + expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
1349 +
1350 + ReactDOM.flushSync(() => {
1351 + setCounter(1);
1352 + });
1353 + // Should not flush yet
1354 + expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
1355 +
1356 + // Run offscreen update
1357 + if (__DEV__) {
1358 + expect(Scheduler).toFlushAndYield(['Bar', 'Bar']);
1359 + } else {
1360 + expect(Scheduler).toFlushAndYield(['Bar']);
1361 + }
1362 + expect(hiddenDiv.innerHTML).toBe('<p>bar 1</p>');
1363 + });
1364 +
1365 it('can render ridiculously large number of roots without triggering infinite update loop error', () => {
1366 class Foo extends React.Component {
1367 componentDidMount() {
packages/react-reconciler/src/ReactFiberBeginWork.js
+10 -1
@@ -973,8 +973,8 @@ function updateHostComponent(current, workInProgress, renderExpirationTime) {
973
974 // Check the host config to see if the children are offscreen/hidden.
975 if (
976 - renderExpirationTime !== Never &&
976 workInProgress.mode & ConcurrentMode &&
977 + renderExpirationTime !== Never &&
978 shouldDeprioritizeSubtree(type, nextProps)
979 ) {
980 // Schedule this fiber to re-render at offscreen priority. Then bailout.
@@ -2133,6 +2133,15 @@ function beginWork(
2133 break;
2134 case HostComponent:
2135 pushHostContext(workInProgress);
2136 + if (
2137 + workInProgress.mode & ConcurrentMode &&
2138 + renderExpirationTime !== Never &&
2139 + shouldDeprioritizeSubtree(workInProgress.type, newProps)
2140 + ) {
2141 + // Schedule this fiber to re-render at offscreen priority. Then bailout.
2142 + workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
2143 + return null;
2144 + }
2145 break;
2146 case ClassComponent: {
2147 const Component = workInProgress.type;
packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.internal.js
+1 -1
@@ -966,7 +966,7 @@ describe('ReactIncrementalSideEffects', () => {
966
967 // However, once we render fully, we will have enough time to finish it all
968 // at once.
969 - expect(Scheduler).toFlushAndYield(['Bar', 'Bar', 'Bar']);
969 + expect(Scheduler).toFlushAndYield(['Bar', 'Bar']);
970 expect(ReactNoop.getChildrenAsJSX()).toEqual(
971 <div>
972 <span prop={1} />