@samitouri / QOS-React / commits / f4c5b2c00b

Updated ReactCompositeComponent to trigger componentWillReceiveProps when either props or context change. Fixes issue #5756

Miles Johnson committed Jan 5, 2016 at 18:14 UTC f4c5b2c00b6b59c0db8efbf7419c8549d7cef7eb
2 files changed +134 -10
src/renderers/shared/reconciler/ReactCompositeComponent.js
+17 -10
@@ -647,12 +647,18 @@ var ReactCompositeComponentMixin = {
647 nextUnmaskedContext
648 ) {
649 var inst = this._instance;
650 -
651 - var nextContext = this._context === nextUnmaskedContext ?
652 - inst.context :
653 - this._processContext(nextUnmaskedContext);
650 + var willReceive = false;
651 + var nextContext;
652 var nextProps;
653
654 + // Determine if the context has changed or not
655 + if (this._context === nextUnmaskedContext) {
656 + nextContext = inst.context;
657 + } else {
658 + nextContext = this._processContext(nextUnmaskedContext);
659 + willReceive = true;
660 + }
661 +
662 // Distinguish between a props update versus a simple state update
663 if (prevParentElement === nextParentElement) {
664 // Skip checking prop types again -- we don't read inst.props to avoid
@@ -660,13 +666,14 @@ var ReactCompositeComponentMixin = {
666 nextProps = nextParentElement.props;
667 } else {
668 nextProps = this._processProps(nextParentElement.props);
663 - // An update here will schedule an update but immediately set
664 - // _pendingStateQueue which will ensure that any state updates gets
665 - // immediately reconciled instead of waiting for the next batch.
669 + willReceive = true;
670 + }
671
667 - if (inst.componentWillReceiveProps) {
668 - inst.componentWillReceiveProps(nextProps, nextContext);
669 - }
672 + // An update here will schedule an update but immediately set
673 + // _pendingStateQueue which will ensure that any state updates gets
674 + // immediately reconciled instead of waiting for the next batch.
675 + if (willReceive && inst.componentWillReceiveProps) {
676 + inst.componentWillReceiveProps(nextProps, nextContext);
677 }
678
679 var nextState = this._processPendingState(nextProps, nextContext);
src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js
+117
@@ -874,6 +874,123 @@ describe('ReactCompositeComponent', function() {
874 expect(div.children[0].id).toBe('aliens');
875 });
876
877 + it('should trigger componentWillReceiveProps for context changes', function() {
878 + var contextChanges = 0;
879 + var propChanges = 0;
880 +
881 + var GrandChild = React.createClass({
882 + contextTypes: {
883 + foo: ReactPropTypes.string.isRequired,
884 + },
885 +
886 + componentWillReceiveProps: function(nextProps, nextContext) {
887 + expect('foo' in nextContext).toBe(true);
888 +
889 + if (nextProps !== this.props) {
890 + propChanges++;
891 + }
892 +
893 + if (nextContext !== this.context) {
894 + contextChanges++;
895 + }
896 + },
897 +
898 + render: function() {
899 + return <span className="grand-child">{this.props.children}</span>;
900 + },
901 + });
902 +
903 + var ChildWithContext = React.createClass({
904 + contextTypes: {
905 + foo: ReactPropTypes.string.isRequired,
906 + },
907 +
908 + componentWillReceiveProps: function(nextProps, nextContext) {
909 + expect('foo' in nextContext).toBe(true);
910 +
911 + if (nextProps !== this.props) {
912 + propChanges++;
913 + }
914 +
915 + if (nextContext !== this.context) {
916 + contextChanges++;
917 + }
918 + },
919 +
920 + render: function() {
921 + return <div className="child-with">{this.props.children}</div>;
922 + },
923 + });
924 +
925 + var ChildWithoutContext = React.createClass({
926 + componentWillReceiveProps: function(nextProps, nextContext) {
927 + expect('foo' in nextContext).toBe(false);
928 +
929 + if (nextProps !== this.props) {
930 + propChanges++;
931 + }
932 +
933 + if (nextContext !== this.context) {
934 + contextChanges++;
935 + }
936 + },
937 +
938 + render: function() {
939 + return <div className="child-without">{this.props.children}</div>;
940 + },
941 + });
942 +
943 + var Parent = React.createClass({
944 + childContextTypes: {
945 + foo: ReactPropTypes.string,
946 + },
947 +
948 + getInitialState() {
949 + return {
950 + foo: 'abc',
951 + };
952 + },
953 +
954 + getChildContext: function() {
955 + return {
956 + foo: this.state.foo,
957 + };
958 + },
959 +
960 + onClick() {
961 + this.setState({
962 + foo: 'def',
963 + });
964 + },
965 +
966 + render: function() {
967 + return <div className="parent" onClick={this.onClick}>{this.props.children}</div>;
968 + },
969 + });
970 +
971 + var div = document.createElement('div');
972 +
973 + ReactDOM.render(
974 + <Parent>
975 + <ChildWithoutContext>
976 + A1
977 + <GrandChild>A2</GrandChild>
978 + </ChildWithoutContext>
979 +
980 + <ChildWithContext>
981 + B1
982 + <GrandChild>B2</GrandChild>
983 + </ChildWithContext>
984 + </Parent>,
985 + div
986 + );
987 +
988 + ReactTestUtils.Simulate.click(div.childNodes[0]);
989 +
990 + expect(propChanges).toBe(0);
991 + expect(contextChanges).toBe(3); // ChildWithContext, GrandChild x 2
992 + });
993 +
994 it('should disallow nested render calls', function() {
995 var Inner = React.createClass({
996 render: function() {