Skip to content

Commit 0375fbd

Browse files
committed
Update input pointers even when shouldComponentUpdate returns false
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 0375fbd

File tree

5 files changed

+56
-13
lines changed

5 files changed

+56
-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+
* memoizes work even if shouldComponentUpdate returns false
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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,43 @@ describe('ReactIncremental', () => {
628628

629629
});
630630

631+
it('memoizes work even if shouldComponentUpdate returns false', () => {
632+
let ops = [];
633+
class Foo extends React.Component {
634+
shouldComponentUpdate(nextProps) {
635+
// this.props is the memoized props. So this should return true for
636+
// every update except the first one.
637+
const shouldUpdate = this.props.step !== 1;
638+
ops.push('shouldComponentUpdate: ' + shouldUpdate);
639+
return shouldUpdate;
640+
}
641+
render() {
642+
ops.push('render');
643+
return <div />;
644+
}
645+
}
646+
647+
ReactNoop.render(<Foo step={1} />);
648+
ReactNoop.flush();
649+
650+
ops = [];
651+
ReactNoop.render(<Foo step={2} />);
652+
ReactNoop.flush();
653+
expect(ops).toEqual([
654+
'shouldComponentUpdate: false',
655+
]);
656+
657+
ops = [];
658+
ReactNoop.render(<Foo step={3} />);
659+
ReactNoop.flush();
660+
expect(ops).toEqual([
661+
// If the memoized props were not updated during last bail out, sCU will
662+
// keep returning false.
663+
'shouldComponentUpdate: true',
664+
'render',
665+
]);
666+
});
667+
631668
it('can update in the middle of a tree using setState', () => {
632669
let instance;
633670
class Bar extends React.Component {

0 commit comments

Comments
 (0)