Simplify composite lifecycle state
We now forbid calling setState or forceUpdate if any component's render function is higher in the stack, not just the component you're trying to update. (We do this already now for React.renderComponent and React.unmountComponentAtNode.) This also has the advantage that we can now remove CompositeLifeCycle.RECEIVING_STATE, making _compositeLifeCycleState easier to reason about (not to mention that RECEIVING_STATE was a confusing name) and making it possible to remove a try/finally from the render path which might help with perf.
Ben Alpert committed
May 19, 2014 at 00:01 UTC
cb20dec85f886996fd41d82a9d5d3857986ccbf2
1 file changed
+55
-63
src/core/ReactCompositeComponent.js
+55
-63
@@ -436,10 +436,11 @@ function validateLifeCycleOnReplaceState(instance) {
436
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
437
'replaceState(...): Can only update a mounted or mounting component.'
438
);
439
- invariant(compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE,
439
+ invariant(
440
+ ReactCurrentOwner.current == null,
441
'replaceState(...): Cannot update during an existing state transition ' +
441
- '(such as within `render`). This could potentially cause an infinite ' +
442
- 'loop so it is forbidden.'
442
+ '(such as within `render`). Render methods should be a pure function ' +
443
+ 'of props and state.'
444
);
445
invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
446
'replaceState(...): Cannot update while unmounting component. This ' +
@@ -637,19 +638,19 @@ function createChainedFunction(one, two) {
638
* Top Row: ReactComponent.ComponentLifeCycle
639
* Low Row: ReactComponent.CompositeLifeCycle
640
*
640
- * +-------+------------------------------------------------------+--------+
641
- * | UN | MOUNTED | UN |
642
- * |MOUNTED| | MOUNTED|
643
- * +-------+------------------------------------------------------+--------+
644
- * | ^--------+ +------+ +------+ +------+ +--------^ |
645
- * | | | | | | | | | | | |
646
- * | 0--|MOUNTING|-0-|RECEIV|-0-|RECEIV|-0-|RECEIV|-0-| UN |--->0 |
647
- * | | | |PROPS | | PROPS| | STATE| |MOUNTING| |
648
- * | | | | | | | | | | | |
649
- * | | | | | | | | | | | |
650
- * | +--------+ +------+ +------+ +------+ +--------+ |
651
- * | | | |
652
- * +-------+------------------------------------------------------+--------+
641
+ * +-------+---------------------------------+--------+
642
+ * | UN | MOUNTED | UN |
643
+ * |MOUNTED| | MOUNTED|
644
+ * +-------+---------------------------------+--------+
645
+ * | ^--------+ +-------+ +--------^ |
646
+ * | | | | | | | |
647
+ * | 0--|MOUNTING|-0-|RECEIVE|-0-| UN |--->0 |
648
+ * | | | |PROPS | |MOUNTING| |
649
+ * | | | | | | | |
650
+ * | | | | | | | |
651
+ * | +--------+ +-------+ +--------+ |
652
+ * | | | |
653
+ * +-------+---------------------------------+--------+
654
*/
655
var CompositeLifeCycle = keyMirror({
656
/**
@@ -666,12 +667,7 @@ var CompositeLifeCycle = keyMirror({
667
* Components that are mounted and receiving new props respond to state
668
* changes differently.
669
*/
669
- RECEIVING_PROPS: null,
670
- /**
671
- * Components that are mounted and receiving new state are guarded against
672
- * additional state changes.
673
- */
674
- RECEIVING_STATE: null
670
+ RECEIVING_PROPS: null
671
});
672
673
/**
@@ -1010,51 +1006,47 @@ var ReactCompositeComponentMixin = {
1006
}
1007
}
1008
1013
- this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
1009
+ this._compositeLifeCycleState = null;
1010
1011
var nextState = this._pendingState || this.state;
1012
this._pendingState = null;
1013
1018
- try {
1019
- var shouldUpdate =
1020
- this._pendingForceUpdate ||
1021
- !this.shouldComponentUpdate ||
1022
- this.shouldComponentUpdate(nextProps, nextState, nextContext);
1023
-
1024
- if (__DEV__) {
1025
- if (typeof shouldUpdate === "undefined") {
1026
- console.warn(
1027
- (this.constructor.displayName || 'ReactCompositeComponent') +
1028
- '.shouldComponentUpdate(): Returned undefined instead of a ' +
1029
- 'boolean value. Make sure to return true or false.'
1030
- );
1031
- }
1032
- }
1014
+ var shouldUpdate =
1015
+ this._pendingForceUpdate ||
1016
+ !this.shouldComponentUpdate ||
1017
+ this.shouldComponentUpdate(nextProps, nextState, nextContext);
1018
1034
- if (shouldUpdate) {
1035
- this._pendingForceUpdate = false;
1036
- // Will set `this.props`, `this.state` and `this.context`.
1037
- this._performComponentUpdate(
1038
- nextDescriptor,
1039
- nextProps,
1040
- nextState,
1041
- nextContext,
1042
- transaction
1019
+ if (__DEV__) {
1020
+ if (typeof shouldUpdate === "undefined") {
1021
+ console.warn(
1022
+ (this.constructor.displayName || 'ReactCompositeComponent') +
1023
+ '.shouldComponentUpdate(): Returned undefined instead of a ' +
1024
+ 'boolean value. Make sure to return true or false.'
1025
);
1044
- } else {
1045
- // If it's determined that a component should not update, we still want
1046
- // to set props and state.
1047
- this._descriptor = nextDescriptor;
1048
- this.props = nextProps;
1049
- this.state = nextState;
1050
- this.context = nextContext;
1051
-
1052
- // Owner cannot change because shouldUpdateReactComponent doesn't allow
1053
- // it. TODO: Remove this._owner completely.
1054
- this._owner = nextDescriptor._owner;
1026
}
1056
- } finally {
1057
- this._compositeLifeCycleState = null;
1027
+ }
1028
+
1029
+ if (shouldUpdate) {
1030
+ this._pendingForceUpdate = false;
1031
+ // Will set `this.props`, `this.state` and `this.context`.
1032
+ this._performComponentUpdate(
1033
+ nextDescriptor,
1034
+ nextProps,
1035
+ nextState,
1036
+ nextContext,
1037
+ transaction
1038
+ );
1039
+ } else {
1040
+ // If it's determined that a component should not update, we still want
1041
+ // to set props and state.
1042
+ this._descriptor = nextDescriptor;
1043
+ this.props = nextProps;
1044
+ this.state = nextState;
1045
+ this.context = nextContext;
1046
+
1047
+ // Owner cannot change because shouldUpdateReactComponent doesn't allow
1048
+ // it. TODO: Remove this._owner completely.
1049
+ this._owner = nextDescriptor._owner;
1050
}
1051
},
1052
@@ -1195,10 +1187,10 @@ var ReactCompositeComponentMixin = {
1187
'components.'
1188
);
1189
invariant(
1198
- compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
1199
- compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
1190
+ compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING &&
1191
+ ReactCurrentOwner.current == null,
1192
'forceUpdate(...): Cannot force an update while unmounting component ' +
1201
- 'or during an existing state transition (such as within `render`).'
1193
+ 'or within a `render` function.'
1194
);
1195
this._pendingForceUpdate = true;
1196
ReactUpdates.enqueueUpdate(this, callback);