Skip to content

Commit bf0583b

Browse files
author
Brian Vaughn
committed
Better handle setState() calls from cWM or cWRP
1 parent e8ef8fa commit bf0583b

File tree

2 files changed

+106
-75
lines changed

2 files changed

+106
-75
lines changed

src/renderers/dom/test/__tests__/ReactTestUtils-test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,31 @@ describe('ReactTestUtils', () => {
350350
expect(result).toEqual(<div>doovy</div>);
351351
});
352352

353+
it('can setState in componentWillReceiveProps when shallow rendering', () => {
354+
class SimpleComponent extends React.Component {
355+
state = {count: 0};
356+
357+
componentWillReceiveProps(nextProps) {
358+
if (nextProps.updateState) {
359+
this.setState({count: 1});
360+
}
361+
}
362+
363+
render() {
364+
return <div>{this.state.count}</div>;
365+
}
366+
}
367+
368+
const shallowRenderer = createRenderer();
369+
let result = shallowRenderer.render(
370+
<SimpleComponent updateState={false} />,
371+
);
372+
expect(result.props.children).toEqual(0);
373+
374+
result = shallowRenderer.render(<SimpleComponent updateState={true} />);
375+
expect(result.props.children).toEqual(1);
376+
});
377+
353378
it('can setState with an updater function', () => {
354379
let instance;
355380

src/renderers/testing/ReactShallowRenderer.js

Lines changed: 81 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class ReactShallowRenderer {
3131
this._instance = null;
3232
this._newState = null;
3333
this._rendered = null;
34+
this._rendering = false;
3435
this._updater = new Updater(this);
3536
}
3637

@@ -59,17 +60,16 @@ class ReactShallowRenderer {
5960
element.type,
6061
);
6162

63+
if (this._rendering) {
64+
return;
65+
}
66+
67+
this._rendering = true;
6268
this._element = element;
6369
this._context = context;
6470

6571
if (this._instance) {
66-
this._rendered = updateClassComponent(
67-
this._instance,
68-
this._rendered,
69-
element.props,
70-
this._newState,
71-
context,
72-
);
72+
this._updateClassComponent(element.props, context);
7373
} else {
7474
if (shouldConstruct(element.type)) {
7575
this._instance = new element.type(element.props, context);
@@ -88,17 +88,14 @@ class ReactShallowRenderer {
8888
ReactDebugCurrentFrame.element = null;
8989
}
9090

91-
this._rendered = mountClassComponent(
92-
this._instance,
93-
element.props,
94-
context,
95-
this._updater,
96-
);
91+
this._mountClassComponent(element.props, context);
9792
} else {
9893
this._rendered = element.type(element.props, context);
9994
}
10095
}
10196

97+
this._rendering = false;
98+
10299
return this.getRenderOutput();
103100
}
104101

@@ -115,6 +112,77 @@ class ReactShallowRenderer {
115112
this._rendered = null;
116113
this._instance = null;
117114
}
115+
116+
_mountClassComponent(props, context) {
117+
this._instance.context = context;
118+
this._instance.props = props;
119+
this._instance.state = this._instance.state || emptyObject;
120+
this._instance.updater = this._updater;
121+
122+
if (typeof this._instance.componentWillMount === 'function') {
123+
const beforeState = this._newState;
124+
125+
this._instance.componentWillMount();
126+
127+
// setState may have been called during cWM
128+
if (beforeState !== this._newState) {
129+
this._instance.state = this._newState || emptyObject;
130+
}
131+
}
132+
133+
this._rendered = this._instance.render();
134+
135+
// Calling cDU might lead to problems with host component references.
136+
// Since our components aren't really mounted, refs won't be available.
137+
// if (typeof this._instance.componentDidMount === 'function') {
138+
// this._instance.componentDidMount();
139+
// }
140+
}
141+
142+
_updateClassComponent(props, context) {
143+
const oldProps = this._instance.props;
144+
const oldState = this._instance.state;
145+
146+
if (
147+
oldProps !== props &&
148+
typeof this._instance.componentWillReceiveProps === 'function'
149+
) {
150+
this._instance.componentWillReceiveProps(props);
151+
}
152+
153+
// Read state after cWRP in case it calls setState
154+
const state = this._newState || emptyObject;
155+
156+
if (typeof this._instance.shouldComponentUpdate === 'function') {
157+
if (
158+
this._instance.shouldComponentUpdate(props, state, context) === false
159+
) {
160+
this._instance.context = context;
161+
this._instance.props = props;
162+
this._instance.state = state;
163+
164+
return;
165+
}
166+
}
167+
168+
if (typeof this._instance.componentWillUpdate === 'function') {
169+
this._instance.componentWillUpdate(props, state, context);
170+
}
171+
172+
this._instance.context = context;
173+
this._instance.props = props;
174+
this._instance.state = state;
175+
176+
this._rendered = this._instance.render();
177+
178+
// The 15.x shallow renderer triggered cDU for setState() calls only.
179+
if (
180+
oldState !== state &&
181+
typeof this._instance.componentDidUpdate === 'function'
182+
) {
183+
this._instance.componentDidUpdate(oldProps, oldState);
184+
}
185+
}
118186
}
119187

120188
class Updater {
@@ -158,70 +226,8 @@ function getName(type, instance) {
158226
null;
159227
}
160228

161-
function mountClassComponent(instance, props, context, updater) {
162-
instance.context = context;
163-
instance.props = props;
164-
instance.state = instance.state || emptyObject;
165-
instance.updater = updater;
166-
167-
if (typeof instance.componentWillMount === 'function') {
168-
instance.componentWillMount();
169-
}
170-
171-
const rendered = instance.render();
172-
173-
// Calling cDU might lead to problems with host component references.
174-
// Since our components aren't really mounted, refs won't be available.
175-
// if (typeof instance.componentDidMount === 'function') {
176-
// instance.componentDidMount();
177-
// }
178-
179-
return rendered;
180-
}
181-
182229
function shouldConstruct(Component) {
183230
return !!(Component.prototype && Component.prototype.isReactComponent);
184231
}
185232

186-
function updateClassComponent(instance, rendered, props, state, context) {
187-
state = state || emptyObject;
188-
189-
const oldProps = instance.props;
190-
const oldState = instance.state;
191-
192-
if (
193-
oldProps !== props &&
194-
typeof instance.componentWillReceiveProps === 'function'
195-
) {
196-
instance.componentWillReceiveProps(props);
197-
}
198-
199-
if (typeof instance.shouldComponentUpdate === 'function') {
200-
if (instance.shouldComponentUpdate(props, state, context) === false) {
201-
instance.context = context;
202-
instance.props = props;
203-
instance.state = state;
204-
205-
return rendered;
206-
}
207-
}
208-
209-
if (typeof instance.componentWillUpdate === 'function') {
210-
instance.componentWillUpdate(props, state, context);
211-
}
212-
213-
instance.context = context;
214-
instance.props = props;
215-
instance.state = state;
216-
217-
rendered = instance.render();
218-
219-
// The 15.x shallow renderer triggered cDU for setState() calls only.
220-
if (oldState !== state && typeof instance.componentDidUpdate === 'function') {
221-
instance.componentDidUpdate(oldProps, oldState);
222-
}
223-
224-
return rendered;
225-
}
226-
227233
module.exports = ReactShallowRenderer;

0 commit comments

Comments
 (0)