Skip to content

Commit 3080d32

Browse files
committed
Don't allow recursive sync updates during reconciliation (begin phase)
We can't allow this because it may lead to infinite loops. Recursion is only safe during the commit phase. Prints a warning and downgrades to Task priority.
1 parent 62fc5b7 commit 3080d32

13 files changed

+84
-56
lines changed

scripts/fiber/tests-failing.txt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ src/renderers/shared/__tests__/ReactPerf-test.js
5656
* should include render time of functional components
5757
* should not count time in a portal towards lifecycle method
5858
* should work when measurement starts during reconciliation
59-
* should not print errant warnings if portal throws in render()
60-
* should not print errant warnings if portal throws in componentWillMount()
6159

6260
src/renderers/shared/hooks/__tests__/ReactComponentTreeHook-test.js
6361
* can be retrieved by ID
@@ -72,7 +70,6 @@ src/renderers/shared/shared/__tests__/ReactComponentLifeCycle-test.js
7270
* should carry through each of the phases of setup
7371

7472
src/renderers/shared/shared/__tests__/ReactCompositeComponent-test.js
75-
* should disallow nested render calls
7673
* should update refs if shouldComponentUpdate gives false
7774

7875
src/renderers/shared/shared/__tests__/ReactEmptyComponent-test.js
@@ -95,5 +92,3 @@ src/renderers/shared/shared/__tests__/ReactUpdates-test.js
9592

9693
src/renderers/shared/shared/__tests__/refs-test.js
9794
* Should increase refs with an increase in divs
98-
* attaches, detaches from fiber component with stack layer
99-
* attaches, detaches from stack component with fiber layer

scripts/fiber/tests-passing-except-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ src/renderers/shared/shared/__tests__/ReactCompositeComponent-test.js
112112
* should warn about `setState` on unmounted components
113113
* should warn about `setState` in render
114114
* should warn about `setState` in getChildContext
115+
* should disallow nested render calls
115116

116117
src/renderers/shared/shared/__tests__/ReactMultiChild-test.js
117118
* should warn for using maps as children with owner info

scripts/fiber/tests-passing.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,8 @@ src/renderers/shared/__tests__/ReactPerf-test.js
11171117
* should not print errant warnings if render() throws
11181118
* should not print errant warnings if componentWillMount() throws
11191119
* should not print errant warnings if componentDidMount() throws
1120+
* should not print errant warnings if portal throws in render()
1121+
* should not print errant warnings if portal throws in componentWillMount()
11201122
* should not print errant warnings if portal throws in componentDidMount()
11211123

11221124
src/renderers/shared/fiber/__tests__/ReactCoroutine-test.js
@@ -1581,6 +1583,8 @@ src/renderers/shared/shared/__tests__/refs-test.js
15811583
* ref called correctly for stateless component when __DEV__ = false
15821584
* ref called correctly for stateless component when __DEV__ = true
15831585
* coerces numbers to strings
1586+
* attaches, detaches from fiber component with stack layer
1587+
* attaches, detaches from stack component with fiber layer
15841588

15851589
src/renderers/shared/shared/event/__tests__/EventPluginHub-test.js
15861590
* should prevent non-function listeners, at dispatch

src/renderers/dom/fiber/ReactDOMFiber.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@ type HostContextDev = {
6767
};
6868
type HostContextProd = string;
6969
type HostContext = HostContextDev | HostContextProd;
70-
71-
let eventsEnabled : ?boolean = null;
72-
let selectionInformation : ?mixed = null;
70+
type CommitInfo = {
71+
eventsEnabled: boolean,
72+
selectionInformation: mixed,
73+
};
7374

7475
var ELEMENT_NODE_TYPE = 1;
7576
var DOC_NODE_TYPE = 9;
@@ -123,17 +124,18 @@ var DOMRenderer = ReactFiberReconciler({
123124
return getChildNamespace(parentNamespace, type);
124125
},
125126

126-
prepareForCommit() : void {
127-
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
127+
prepareForCommit() : CommitInfo {
128+
const eventsEnabled = ReactBrowserEventEmitter.isEnabled();
128129
ReactBrowserEventEmitter.setEnabled(false);
129-
selectionInformation = ReactInputSelection.getSelectionInformation();
130+
return {
131+
eventsEnabled,
132+
selectionInformation: ReactInputSelection.getSelectionInformation(),
133+
};
130134
},
131135

132-
resetAfterCommit() : void {
133-
ReactInputSelection.restoreSelection(selectionInformation);
134-
selectionInformation = null;
135-
ReactBrowserEventEmitter.setEnabled(eventsEnabled);
136-
eventsEnabled = null;
136+
resetAfterCommit(commitInfo : CommitInfo) : void {
137+
ReactInputSelection.restoreSelection(commitInfo.selectionInformation);
138+
ReactBrowserEventEmitter.setEnabled(commitInfo.eventsEnabled);
137139
},
138140

139141
createInstance(

src/renderers/shared/__tests__/ReactPerf-test.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ describe('ReactPerf', () => {
1616
var ReactDOM;
1717
var ReactPerf;
1818
var ReactTestUtils;
19+
var ReactDOMFeatureFlags;
1920
var emptyFunction;
2021

2122
var App;
@@ -38,6 +39,7 @@ describe('ReactPerf', () => {
3839
ReactDOM = require('ReactDOM');
3940
ReactPerf = require('ReactPerf');
4041
ReactTestUtils = require('ReactTestUtils');
42+
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
4143
emptyFunction = require('emptyFunction');
4244

4345
App = class extends React.Component {
@@ -614,7 +616,7 @@ describe('ReactPerf', () => {
614616
}
615617
}
616618
class EvilPortal extends React.Component {
617-
componentWillMount() {
619+
componentDidMount() {
618620
var portalContainer = document.createElement('div');
619621
ReactDOM.render(<Evil />, portalContainer);
620622
}
@@ -644,6 +646,7 @@ describe('ReactPerf', () => {
644646
it('should not print errant warnings if portal throws in componentWillMount()', () => {
645647
var container = document.createElement('div');
646648
var thrownErr = new Error('Muhaha!');
649+
spyOn(console, 'error');
647650

648651
class Evil extends React.Component {
649652
componentWillMount() {
@@ -679,6 +682,17 @@ describe('ReactPerf', () => {
679682
}
680683
ReactDOM.unmountComponentAtNode(container);
681684
ReactPerf.stop();
685+
686+
if (ReactDOMFeatureFlags.useFiber) {
687+
// A sync `render` inside cWM will print a warning. That should be the
688+
// only warning.
689+
expect(console.error.calls.count()).toEqual(1);
690+
expect(console.error.calls.argsFor(0)[0]).toMatch(
691+
/Render methods should be a pure function of props and state/
692+
);
693+
} else {
694+
expect(console.error.calls.count()).toEqual(0);
695+
}
682696
});
683697

684698
it('should not print errant warnings if portal throws in componentDidMount()', () => {
@@ -694,7 +708,7 @@ describe('ReactPerf', () => {
694708
}
695709
}
696710
class EvilPortal extends React.Component {
697-
componentWillMount() {
711+
componentDidMount() {
698712
var portalContainer = document.createElement('div');
699713
ReactDOM.render(<Evil />, portalContainer);
700714
}

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ if (__DEV__) {
6666
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
6767
}
6868

69-
module.exports = function<T, P, I, TI, C, CX>(
70-
config : HostConfig<T, P, I, TI, C, CX>,
69+
module.exports = function<T, P, I, TI, C, CX, CI>(
70+
config : HostConfig<T, P, I, TI, C, CX, CI>,
7171
hostContext : HostContext<C, CX>,
7272
scheduleUpdate : (fiber : Fiber, priorityLevel : PriorityLevel) => void,
7373
getPriorityContext : () => PriorityLevel,

src/renderers/shared/fiber/ReactFiberCommitWork.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ var {
3333
ContentReset,
3434
} = require('ReactTypeOfSideEffect');
3535

36-
module.exports = function<T, P, I, TI, C, CX>(
37-
config : HostConfig<T, P, I, TI, C, CX>,
36+
module.exports = function<T, P, I, TI, C, CX, CI>(
37+
config : HostConfig<T, P, I, TI, C, CX, CI>,
3838
hostContext : HostContext<C, CX>,
3939
captureError : (failedFiber : Fiber, error: Error) => ?Fiber
4040
) {

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ if (__DEV__) {
4646
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
4747
}
4848

49-
module.exports = function<T, P, I, TI, C, CX>(
50-
config : HostConfig<T, P, I, TI, C, CX>,
49+
module.exports = function<T, P, I, TI, C, CX, CI>(
50+
config : HostConfig<T, P, I, TI, C, CX, CI>,
5151
hostContext : HostContext<C, CX>,
5252
) {
5353
const {

src/renderers/shared/fiber/ReactFiberHostContext.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export type HostContext<C, CX> = {
3434
resetHostContainer() : void,
3535
};
3636

37-
module.exports = function<T, P, I, TI, C, CX>(
38-
config : HostConfig<T, P, I, TI, C, CX>
37+
module.exports = function<T, P, I, TI, C, CX, CI>(
38+
config : HostConfig<T, P, I, TI, C, CX, CI>
3939
) : HostContext<C, CX> {
4040
const {
4141
getChildHostContext,

src/renderers/shared/fiber/ReactFiberReconciler.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export type Deadline = {
4343

4444
type OpaqueNode = Fiber;
4545

46-
export type HostConfig<T, P, I, TI, C, CX> = {
46+
export type HostConfig<T, P, I, TI, C, CX, CI> = {
4747

4848
getRootHostContext(rootContainerInstance : C) : CX,
4949
getChildHostContext(parentHostContext : CX, type : T) : CX,
@@ -68,8 +68,8 @@ export type HostConfig<T, P, I, TI, C, CX> = {
6868
scheduleAnimationCallback(callback : () => void) : void,
6969
scheduleDeferredCallback(callback : (deadline : Deadline) => void) : void,
7070

71-
prepareForCommit() : void,
72-
resetAfterCommit() : void,
71+
prepareForCommit() : CI,
72+
resetAfterCommit(commitInfo : CI) : void,
7373

7474
useSyncScheduling ?: boolean,
7575
};
@@ -99,7 +99,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
9999
parentContext;
100100
});
101101

102-
module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
102+
module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, TI, C, CX, CI>) : Reconciler<C, I, TI> {
103103

104104
var {
105105
scheduleUpdate,

0 commit comments

Comments
 (0)