Switch to parent-based context. Fixes #2112.
Jim committed
Apr 7, 2015 at 14:02 UTC
7d4491753158afc839f2934367ad2f7878ff7283
5 files changed
+36
-287
src/classic/element/ReactElement.js
-4
@@ -99,10 +99,6 @@ var ReactElement = function(type, key, ref, owner, context, props) {
99
// Record the component responsible for creating this element.
100
this._owner = owner;
101
102
- // TODO: Deprecate withContext, and then the context becomes accessible
103
- // through the owner.
104
- this._context = context;
105
-
102
if (__DEV__) {
103
// The validation flag and props are currently mutative. We put them on
104
// an external backing store so that we can freeze the whole object.
src/classic/element/__tests__/ReactElement-test.js
-24
@@ -86,30 +86,6 @@ describe('ReactElement', function() {
86
expect(element.props).toEqual({foo:'56'});
87
});
88
89
- it('preserves the legacy context on the element', function() {
90
- var Component = React.createFactory(ComponentClass);
91
- var element;
92
-
93
- var Wrapper = React.createClass({
94
- childContextTypes: {
95
- foo: React.PropTypes.string
96
- },
97
- getChildContext: function() {
98
- return {foo: 'bar'};
99
- },
100
- render: function() {
101
- element = Component();
102
- return element;
103
- }
104
- });
105
-
106
- ReactTestUtils.renderIntoDocument(
107
- React.createElement(Wrapper)
108
- );
109
-
110
- expect(element._context).toEqual({foo: 'bar'});
111
- });
112
-
89
it('preserves the owner on the element', function() {
90
var Component = React.createFactory(ComponentClass);
91
var element;
src/core/ReactCompositeComponent.js
+2
-39
@@ -125,7 +125,7 @@ var ReactCompositeComponentMixin = {
125
this._rootNodeID = rootID;
126
127
var publicProps = this._processProps(this._currentElement.props);
128
- var publicContext = this._processContext(this._currentElement._context);
128
+ var publicContext = this._processContext(context);
129
130
var Component = ReactNativeComponent.getComponentClassForElement(
131
this._currentElement
@@ -158,10 +158,6 @@ var ReactCompositeComponentMixin = {
158
// Store a reference from the instance back to the internal representation
159
ReactInstanceMap.set(inst, this);
160
161
- if (__DEV__) {
162
- this._warnIfContextsDiffer(this._currentElement._context, context);
163
- }
164
-
161
if (__DEV__) {
162
// Since plain JS classes are defined without any special initialization
163
// logic, we can not catch common errors early. Therefore, we have to
@@ -529,30 +525,6 @@ var ReactCompositeComponentMixin = {
525
}
526
},
527
532
- /**
533
- * Compare two contexts, warning if they are different
534
- * TODO: Remove this check when owner-context is removed
535
- */
536
- _warnIfContextsDiffer: function(ownerBasedContext, parentBasedContext) {
537
- ownerBasedContext = this._maskContext(ownerBasedContext);
538
- parentBasedContext = this._maskContext(parentBasedContext);
539
- var parentKeys = Object.keys(parentBasedContext).sort();
540
- var displayName = this.getName() || 'ReactCompositeComponent';
541
- for (var i = 0; i < parentKeys.length; i++) {
542
- var key = parentKeys[i];
543
- warning(
544
- ownerBasedContext[key] === parentBasedContext[key],
545
- 'owner-based and parent-based contexts differ ' +
546
- '(values: `%s` vs `%s`) for key (%s) while mounting %s ' +
547
- '(see: http://fb.me/react-context-by-parent)',
548
- ownerBasedContext[key],
549
- parentBasedContext[key],
550
- key,
551
- displayName
552
- );
553
- }
554
- },
555
-
528
/**
529
* Perform an update to a mounted component. The componentWillReceiveProps and
530
* shouldComponentUpdate methods are called, then (assuming the update isn't
@@ -582,18 +554,9 @@ var ReactCompositeComponentMixin = {
554
555
// Distinguish between a props update versus a simple state update
556
if (prevParentElement !== nextParentElement) {
585
- nextContext = this._processContext(nextParentElement._context);
557
+ nextContext = this._processContext(nextUnmaskedContext);
558
nextProps = this._processProps(nextParentElement.props);
559
588
- if (__DEV__) {
589
- if (nextUnmaskedContext != null) {
590
- this._warnIfContextsDiffer(
591
- nextParentElement._context,
592
- nextUnmaskedContext
593
- );
594
- }
595
- }
596
-
560
// An update here will schedule an update but immediately set
561
// _pendingStateQueue which will ensure that any state updates gets
562
// immediately reconciled instead of waiting for the next batch.
src/core/ReactContext.js
+1
-43
@@ -11,11 +11,7 @@
11
12
'use strict';
13
14
-var assign = require('Object.assign');
14
var emptyObject = require('emptyObject');
16
-var warning = require('warning');
17
-
18
-var didWarn = false;
15
16
/**
17
* Keeps track of the current context.
@@ -29,45 +25,7 @@ var ReactContext = {
25
* @internal
26
* @type {object}
27
*/
32
- current: emptyObject,
33
-
34
- /**
35
- * Temporarily extends the current context while executing scopedCallback.
36
- *
37
- * A typical use case might look like
38
- *
39
- * render: function() {
40
- * var children = ReactContext.withContext({foo: 'foo'}, () => (
41
- *
42
- * ));
43
- * return <div>{children}</div>;
44
- * }
45
- *
46
- * @param {object} newContext New context to merge into the existing context
47
- * @param {function} scopedCallback Callback to run with the new context
48
- * @return {ReactComponent|array<ReactComponent>}
49
- */
50
- withContext: function(newContext, scopedCallback) {
51
- if (__DEV__) {
52
- warning(
53
- didWarn,
54
- 'withContext is deprecated and will be removed in a future version. ' +
55
- 'Use a wrapper component with getChildContext instead.'
56
- );
57
-
58
- didWarn = true;
59
- }
60
-
61
- var result;
62
- var previousContext = ReactContext.current;
63
- ReactContext.current = assign({}, previousContext, newContext);
64
- try {
65
- result = scopedCallback();
66
- } finally {
67
- ReactContext.current = previousContext;
68
- }
69
- return result;
70
- }
28
+ current: emptyObject
29
30
};
31
src/core/__tests__/ReactCompositeComponent-test.js
+33
-177
@@ -73,12 +73,7 @@ describe('ReactCompositeComponent', function() {
73
}
74
});
75
76
- // Ignore the first warning which is fired by using withContext at all.
77
- // That way we don't have to reset and assert it on every subsequent test.
78
- // This will be killed soon anyway.
76
console.error = mocks.getMockFunction();
80
- React.withContext({}, function() { });
81
-
77
spyOn(console, 'error');
78
});
79
@@ -621,178 +616,6 @@ describe('ReactCompositeComponent', function() {
616
reactComponentExpect(childInstance).scalarContextEqual({foo: 'bar', depth: 0});
617
});
618
624
- it('warn if context keys differ', function() {
625
- var Component = React.createClass({
626
- contextTypes: {
627
- foo: ReactPropTypes.string.isRequired
628
- },
629
-
630
- render: function() {
631
- return <div />;
632
- }
633
- });
634
-
635
- React.withContext({foo: 'bar'}, function() {
636
- ReactTestUtils.renderIntoDocument(<Component />);
637
- });
638
-
639
- expect(console.error.argsForCall.length).toBe(1);
640
- expect(console.error.argsForCall[0][0]).toBe(
641
- 'Warning: owner-based and parent-based contexts differ ' +
642
- '(values: `bar` vs `undefined`) for key (foo) ' +
643
- 'while mounting Component (see: http://fb.me/react-context-by-parent)'
644
- );
645
-
646
- });
647
-
648
- it('warn if context values differ', function() {
649
- var Parent = React.createClass({
650
- childContextTypes: {
651
- foo: ReactPropTypes.string
652
- },
653
-
654
- getChildContext: function() {
655
- return {
656
- foo: "bar"
657
- };
658
- },
659
-
660
- render: function() {
661
- return <div>{this.props.children}</div>;
662
- }
663
- });
664
- var Component = React.createClass({
665
- contextTypes: {
666
- foo: ReactPropTypes.string.isRequired
667
- },
668
-
669
- render: function() {
670
- return <div />;
671
- }
672
- });
673
-
674
- var component = React.withContext({foo: 'noise'}, function() {
675
- return <Component />;
676
- });
677
-
678
- ReactTestUtils.renderIntoDocument(<Parent>{component}</Parent>);
679
-
680
- // Two warnings, one for the component and one for the div
681
- // We may want to make this expect one warning in the future
682
- expect(console.error.argsForCall.length).toBe(1);
683
- expect(console.error.argsForCall[0][0]).toBe(
684
- 'Warning: owner-based and parent-based contexts differ ' +
685
- '(values: `noise` vs `bar`) for key (foo) while mounting Component ' +
686
- '(see: http://fb.me/react-context-by-parent)'
687
- );
688
-
689
- });
690
-
691
- it('should warn if context values differ on update using withContext', function() {
692
- var Parent = React.createClass({
693
- childContextTypes: {
694
- foo: ReactPropTypes.string
695
- },
696
-
697
- getChildContext: function() {
698
- return {
699
- foo: "bar"
700
- };
701
- },
702
-
703
- render: function() {
704
- return <div>{this.props.children}</div>;
705
- }
706
- });
707
-
708
- var Component = React.createClass({
709
- contextTypes: {
710
- foo: ReactPropTypes.string.isRequired
711
- },
712
-
713
- render: function() {
714
- return <div />;
715
- }
716
- });
717
-
718
- var div = document.createElement('div');
719
-
720
- var componentWithSameContext = React.withContext({foo: 'bar'}, function() {
721
- return <Component />;
722
- });
723
- React.render(<Parent>{componentWithSameContext}</Parent>, div);
724
-
725
- expect(console.error.argsForCall.length).toBe(0);
726
-
727
- var componentWithDifferentContext = React.withContext({foo: 'noise'}, function() {
728
- return <Component />;
729
- });
730
- React.render(<Parent>{componentWithDifferentContext}</Parent>, div);
731
-
732
- // Two warnings, one for the component and one for the div
733
- // We may want to make this expect one warning in the future
734
- expect(console.error.argsForCall.length).toBe(1);
735
- expect(console.error.argsForCall[0][0]).toBe(
736
- 'Warning: owner-based and parent-based contexts differ ' +
737
- '(values: `noise` vs `bar`) for key (foo) while mounting Component ' +
738
- '(see: http://fb.me/react-context-by-parent)'
739
- );
740
- });
741
-
742
- it('should warn if context values differ on update using wrapper', function() {
743
- var Parent = React.createClass({
744
- childContextTypes: {
745
- foo: ReactPropTypes.string
746
- },
747
-
748
- getChildContext: function() {
749
- return {
750
- foo: "bar"
751
- };
752
- },
753
-
754
- render: function() {
755
- return <div>{this.props.children}</div>;
756
- }
757
- });
758
-
759
- var Component = React.createClass({
760
- contextTypes: {
761
- foo: ReactPropTypes.string.isRequired
762
- },
763
-
764
- render: function() {
765
- return <div />;
766
- }
767
- });
768
-
769
- var Wrapper = React.createClass({
770
- childContextTypes: {
771
- foo: ReactPropTypes.string
772
- },
773
-
774
- getChildContext: function() {
775
- return {foo: this.props.foo};
776
- },
777
-
778
- render: function() { return <Parent><Component /></Parent>; }
779
-
780
- });
781
-
782
- var div = document.createElement('div');
783
- React.render(<Wrapper foo='bar' />, div);
784
- React.render(<Wrapper foo='noise' />, div);
785
-
786
- // Two warnings, one for the component and one for the div
787
- // We may want to make this expect one warning in the future
788
- expect(console.error.argsForCall.length).toBe(1);
789
- expect(console.error.argsForCall[0][0]).toBe(
790
- 'Warning: owner-based and parent-based contexts differ ' +
791
- '(values: `noise` vs `bar`) for key (foo) while mounting Component ' +
792
- '(see: http://fb.me/react-context-by-parent)'
793
- );
794
- });
795
-
619
it('unmasked context propagates through updates', function() {
620
621
var Leaf = React.createClass({
@@ -996,4 +819,37 @@ describe('ReactCompositeComponent', function() {
819
);
820
});
821
822
+ it('context should be passed down from the parent', function() {
823
+ var Parent = React.createClass({
824
+ childContextTypes: {
825
+ foo: ReactPropTypes.string
826
+ },
827
+
828
+ getChildContext: function() {
829
+ return {
830
+ foo: "bar"
831
+ };
832
+ },
833
+
834
+ render: function() {
835
+ return <div>{this.props.children}</div>;
836
+ }
837
+ });
838
+
839
+ var Component = React.createClass({
840
+ contextTypes: {
841
+ foo: ReactPropTypes.string.isRequired
842
+ },
843
+
844
+ render: function() {
845
+ return <div />;
846
+ }
847
+ });
848
+
849
+ var div = document.createElement('div');
850
+ React.render(<Parent><Component /></Parent>, div);
851
+
852
+ expect(console.error.argsForCall.length).toBe(0);
853
+ });
854
+
855
});