Skip to content

Commit ad0141c

Browse files
committed
Update input pointers even when shouldComponentUpdate returns false
We should be able to reuse work even if it is pre-empted by a high priority update before flushing. Because memoizedProps and memoizedState are read from the instance, the instance's input pointers (props, state, context) should be updated even when shouldComponentUpdate causes a bail-out.
1 parent df6ca0e commit ad0141c

File tree

5 files changed

+68
-13
lines changed

5 files changed

+68
-13
lines changed

scripts/fiber/tests-failing.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js
2-
* does not do a deep comparison
3-
41
src/addons/__tests__/ReactFragment-test.js
52
* should throw if a plain object is used as a child
63
* should throw if a plain object even if it is in an owner

scripts/fiber/tests-passing.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ scripts/error-codes/__tests__/invertObject-test.js
3636

3737
src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js
3838
* provides a default shouldComponentUpdate implementation
39+
* does not do a deep comparison
3940

4041
src/addons/__tests__/ReactFragment-test.js
4142
* warns for numeric keys on objects as children
@@ -1138,6 +1139,7 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
11381139
* can resume work in a bailed subtree within one pass
11391140
* can reuse work done after being preempted
11401141
* can reuse work if shouldComponentUpdate is false, after being preempted
1142+
* can reuse preempted work with shouldComponentUpdate
11411143
* can update in the middle of a tree using setState
11421144
* can queue multiple state updates
11431145
* can use updater form of setState

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,8 @@ module.exports = function<T, P, I, TI, C, CX>(
251251
// If an update was already in progress, we should schedule an Update
252252
// effect even though we're bailing out, so that cWU/cDU are called.
253253
if (current) {
254-
const instance = current.stateNode;
255-
if (instance.props !== current.memoizedProps ||
256-
instance.state !== current.memoizedState) {
254+
if (workInProgress.memoizedProps !== current.memoizedProps ||
255+
workInProgress.memoizedState !== current.memoizedState) {
257256
workInProgress.effectTag |= Update;
258257
}
259258
}

src/renderers/shared/fiber/ReactFiberClassComponent.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,12 @@ module.exports = function(
303303
newState,
304304
newContext
305305
)) {
306+
// Update the existing instance's state, props, and context pointers even
307+
// though we're bailing out.
308+
const instance = workInProgress.stateNode;
309+
instance.props = newProps;
310+
instance.state = newState;
311+
instance.context = newContext;
306312
return false;
307313
}
308314

@@ -385,25 +391,27 @@ module.exports = function(
385391
return false;
386392
}
387393

388-
if (!checkShouldComponentUpdate(
394+
const shouldUpdate = checkShouldComponentUpdate(
389395
workInProgress,
390396
oldProps,
391397
newProps,
392398
newState,
393399
newContext
394-
)) {
395-
// TODO: Should this get the new props/state updated regardless?
396-
return false;
397-
}
400+
);
398401

399-
if (typeof instance.componentWillUpdate === 'function') {
402+
if (shouldUpdate && typeof instance.componentWillUpdate === 'function') {
400403
instance.componentWillUpdate(newProps, newState, newContext);
401404
}
402405

406+
// Update the existing instance's state, props, and context pointers even
407+
// if shouldComponentUpdate returns false. memoizedProps and memoizedState
408+
// are read from the instance, so by always updating them, we ensure that
409+
// work is memoized even when we bail out.
403410
instance.props = newProps;
404411
instance.state = newState;
405412
instance.context = newContext;
406-
return true;
413+
414+
return shouldUpdate;
407415
}
408416

409417
return {

src/renderers/shared/fiber/__tests__/ReactIncremental-test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,55 @@ describe('ReactIncremental', () => {
628628

629629
});
630630

631+
it('can reuse preempted work with shouldComponentUpdate', () => {
632+
let ops = [];
633+
class Foo extends React.Component {
634+
shouldComponentUpdate(nextProps) {
635+
const shouldUpdate = this.props.step !== nextProps.step;
636+
ops.push('shouldComponentUpdate: ' + shouldUpdate);
637+
return shouldUpdate;
638+
}
639+
render() {
640+
ops.push('render');
641+
return <div />;
642+
}
643+
}
644+
645+
ReactNoop.render(<Foo step={1} />);
646+
ReactNoop.flush();
647+
ops = [];
648+
649+
let didFlush = false;
650+
ReactNoop.render(<Foo step={2} />, () => {
651+
didFlush = true;
652+
});
653+
// Begin a low priority update, but stop before it is flushed.
654+
ReactNoop.flushDeferredPri(15);
655+
expect(ops).toEqual([
656+
'shouldComponentUpdate: true',
657+
'render',
658+
]);
659+
expect(didFlush).toEqual(false);
660+
661+
ops = [];
662+
// Preempt the low priority update with a higher one.
663+
ReactNoop.performAnimationWork(() => {
664+
ReactNoop.render(<Foo step={2} />);
665+
});
666+
ReactNoop.flush();
667+
668+
// shouldComponentUpdate should compare new props with preempted props,
669+
// not current props. So it should return false and not re-render.
670+
expect(ops).toEqual([
671+
// There are two pending updates on the root, so sCU is called twice.
672+
// High priority update
673+
'shouldComponentUpdate: false',
674+
// Low priority update
675+
'shouldComponentUpdate: false',
676+
]);
677+
expect(didFlush).toEqual(true);
678+
});
679+
631680
it('can update in the middle of a tree using setState', () => {
632681
let instance;
633682
class Bar extends React.Component {

0 commit comments

Comments
 (0)