Don't bail out of reconsiliation if context changes.
Jim committed
Jun 24, 2015 at 21:14 UTC
289962f6f868df1ccb4620912c7fd806f98a7e47
5 files changed
+119
-24
examples/basic-jsx/index.html
+46
-15
@@ -29,23 +29,54 @@
29
<script src="../../build/react.js"></script>
30
<script src="../../build/JSXTransformer.js"></script>
31
<script type="text/jsx">
32
- var ExampleApplication = React.createClass({
33
- render: function() {
34
- var elapsed = Math.round(this.props.elapsed / 100);
35
- var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
36
- var message =
37
- 'React has been successfully running for ' + seconds + ' seconds.';
32
39
- return <p>{message}</p>;
40
- }
33
+var Top = React.createClass({
34
+ childContextTypes: {
35
+ now: React.PropTypes.any
36
+ },
37
+
38
+ getChildContext: function() {
39
+ return {
40
+ now: this.state.now
41
+ };
42
+ },
43
+
44
+ getInitialState: function() {
45
+ return {
46
+ now: Date.now()
47
+ };
48
+ },
49
+
50
+ componentDidMount: function() {
51
+ setInterval(function() {
52
+ this.setState({
53
+ now: Date.now()
54
});
42
- var start = new Date().getTime();
43
- setInterval(function() {
44
- React.render(
45
- <ExampleApplication elapsed={new Date().getTime() - start} />,
46
- document.getElementById('container')
47
- );
48
- }, 50);
55
+ }.bind(this), 500);
56
+ },
57
+
58
+ render: function() {
59
+ return (
60
+ <div>
61
+ <h1>{this.state.now}</h1>
62
+ <Child />{this.props.children}
63
+ </div>
64
+ );
65
+ }
66
+});
67
+
68
+var Child = React.createClass({
69
+ contextTypes: {
70
+ now: React.PropTypes.any
71
+ },
72
+
73
+ render: function() {
74
+ // I never update
75
+ return <h2>{this.context.now}</h2>;
76
+ }
77
+});
78
+
79
+React.render(<Top><Child/></Top>, document.getElementById('container'));
80
</script>
81
</body>
82
</html>
src/renderers/shared/reconciler/ReactCompositeComponent.js
+3
-4
@@ -519,19 +519,18 @@ var ReactCompositeComponentMixin = {
519
) {
520
var inst = this._instance;
521
522
- var nextContext;
522
+ var nextContext = this._context === nextUnmaskedContext ?
523
+ inst.context :
524
+ this._processContext(nextUnmaskedContext);
525
var nextProps;
526
527
// Distinguish between a props update versus a simple state update
528
if (prevParentElement === nextParentElement) {
527
- nextContext = inst.context;
529
// Skip checking prop types again -- we don't read inst.props to avoid
530
// warning for DOM component props in this upgrade
531
nextProps = nextParentElement.props;
532
} else {
532
- nextContext = this._processContext(nextUnmaskedContext);
533
nextProps = this._processProps(nextParentElement.props);
534
-
534
// An update here will schedule an update but immediately set
535
// _pendingStateQueue which will ensure that any state updates gets
536
// immediately reconciled instead of waiting for the next batch.
src/renderers/shared/reconciler/ReactReconciler.js
+7
-2
@@ -65,8 +65,10 @@ var ReactReconciler = {
65
internalInstance, nextElement, transaction, context
66
) {
67
var prevElement = internalInstance._currentElement;
68
-
69
- if (nextElement === prevElement && nextElement._owner != null) {
68
+ if (nextElement === prevElement &&
69
+ nextElement._owner != null
70
+ // TODO: Shouldn't we need to do this: `&& context === internalInstance._context`
71
+ ) {
72
// Since elements are immutable after the owner is rendered,
73
// we can do a cheap identity compare here to determine if this is a
74
// superfluous reconcile. It's possible for state to be mutable but such
@@ -74,6 +76,9 @@ var ReactReconciler = {
76
// the element. We explicitly check for the existence of an owner since
77
// it's possible for an element created outside a composite to be
78
// deeply mutated and reused.
79
+
80
+ // TODO: Bailing out early is just a perf optimization right?
81
+ // TODO: Removing the return statement should affect correctness?
82
return;
83
}
84
src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js
+61
@@ -609,6 +609,67 @@ describe('ReactCompositeComponent', function() {
609
expect(React.findDOMNode(component).innerHTML).toBe('bar');
610
});
611
612
+ it('should pass context when re-rendered for static child', function() {
613
+ var parentInstance = null;
614
+ var childInstance = null;
615
+
616
+ var Parent = React.createClass({
617
+ childContextTypes: {
618
+ foo: ReactPropTypes.string,
619
+ flag: ReactPropTypes.bool,
620
+ },
621
+
622
+ getChildContext: function() {
623
+ return {
624
+ foo: 'bar',
625
+ flag: this.state.flag,
626
+ };
627
+ },
628
+
629
+ getInitialState: function() {
630
+ return {
631
+ flag: false,
632
+ };
633
+ },
634
+
635
+ render: function() {
636
+ return React.Children.only(this.props.children);
637
+ },
638
+ });
639
+
640
+ var Middle = React.createClass({
641
+ render: function() {
642
+ return this.props.children;
643
+ },
644
+ });
645
+
646
+ var Child = React.createClass({
647
+ contextTypes: {
648
+ foo: ReactPropTypes.string,
649
+ flag: ReactPropTypes.bool,
650
+ },
651
+
652
+ render: function() {
653
+ childInstance = this;
654
+ return <span>Child</span>;
655
+ },
656
+ });
657
+
658
+ parentInstance = ReactTestUtils.renderIntoDocument(
659
+ <Parent><Middle><Child /></Middle></Parent>
660
+ );
661
+
662
+ expect(parentInstance.state.flag).toBe(false);
663
+ reactComponentExpect(childInstance).scalarContextEqual({foo: 'bar', flag: false});
664
+
665
+ parentInstance.setState({flag: true});
666
+ expect(parentInstance.state.flag).toBe(true);
667
+
668
+ expect(console.error.argsForCall.length).toBe(0);
669
+
670
+ reactComponentExpect(childInstance).scalarContextEqual({foo: 'bar', flag: true});
671
+ });
672
+
673
it('should pass context transitively', function() {
674
var childInstance = null;
675
var grandchildInstance = null;
src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js
+2
-3
@@ -347,14 +347,14 @@ describe('ReactUpdates', function() {
347
348
render: function() {
349
numMiddleRenders++;
350
- return <div>{this.props.children}</div>;
350
+ return React.Children.only(this.props.children);
351
},
352
});
353
354
var Bottom = React.createClass({
355
render: function() {
356
numBottomRenders++;
357
- return <span />;
357
+ return null;
358
},
359
});
360
@@ -463,7 +463,6 @@ describe('ReactUpdates', function() {
463
464
expectUpdates(desiredWillUpdates, desiredDidUpdates);
465
}
466
-
466
testUpdates(
467
[root.refs.switcher.refs.box, root.refs.switcher],
468
// Owner-child relationships have inverse will and did