@samitouri / QOS-React-2 / commits / 1d220ce0b7

Bug fix: SSR setState in diff components don't mix (#12323)

Previously, the `queue` and `replace` arguments were leaking across loops even though they should be captured.

Sophie Alpert committed Mar 3, 2018 at 10:27 UTC 1d220ce0b7bc7f4843cf90f1fe8933b9fcba9deb
2 files changed +58 -32
packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js
+34
@@ -226,4 +226,38 @@ describe('ReactDOMServerLifecycles', () => {
226 ReactDOMServer.renderToString(<Component />);
227 expect(log).toEqual(['componentWillMount', 'UNSAFE_componentWillMount']);
228 });
229 +
230 + it('tracks state updates across components', () => {
231 + class Outer extends React.Component {
232 + UNSAFE_componentWillMount() {
233 + this.setState({x: 1});
234 + }
235 + render() {
236 + return <Inner updateParent={this.updateParent}>{this.state.x}</Inner>;
237 + }
238 + updateParent = () => {
239 + this.setState({x: 3});
240 + };
241 + }
242 + class Inner extends React.Component {
243 + UNSAFE_componentWillMount() {
244 + this.setState({x: 2});
245 + this.props.updateParent();
246 + }
247 + render() {
248 + return <div>{this.props.children + '-' + this.state.x}</div>;
249 + }
250 + }
251 + expect(() => {
252 + // Shouldn't be 1-3.
253 + expect(ReactDOMServer.renderToStaticMarkup(<Outer />)).toBe(
254 + '<div>1-2</div>',
255 + );
256 + }).toWarnDev(
257 + 'Warning: setState(...): Can only update a mounting component. This ' +
258 + 'usually means you called setState() outside componentWillMount() on ' +
259 + 'the server. This is a no-op.\n\n' +
260 + 'Please check the code for the Outer component.',
261 + );
262 + });
263 });
packages/react-dom/src/server/ReactPartialRenderer.js
+24 -32
@@ -381,36 +381,26 @@ function resolve(
381 child: mixed,
382 context: Object,
383 |} {
384 - let element: ReactElement;
385 -
386 - let Component;
387 - let publicContext;
388 - let inst, queue, replace;
389 - let updater;
390 -
391 - let initialState;
392 - let oldQueue, oldReplace;
393 - let nextState, dontMutate;
394 - let partial, partialState;
395 -
396 - let childContext;
397 - let childContextTypes, contextKey;
398 -
384 while (React.isValidElement(child)) {
385 // Safe because we just checked it's an element.
401 - element = ((child: any): ReactElement);
386 + let element: ReactElement = (child: any);
387 + let Component = element.type;
388 if (__DEV__) {
389 pushElementToDebugStack(element);
390 }
405 - Component = element.type;
391 if (typeof Component !== 'function') {
392 break;
393 }
409 - publicContext = processContext(Component, context);
394 + processChild(element, Component);
395 + }
396 +
397 + // Extra closure so queue and replace can be captured properly
398 + function processChild(element, Component) {
399 + let publicContext = processContext(Component, context);
400
411 - queue = [];
412 - replace = false;
413 - updater = {
401 + let queue = [];
402 + let replace = false;
403 + let updater = {
404 isMounted: function(publicInstance) {
405 return false;
406 },
@@ -433,6 +423,7 @@ function resolve(
423 },
424 };
425
426 + let inst;
427 if (shouldConstruct(Component)) {
428 inst = new Component(element.props, publicContext, updater);
429
@@ -453,7 +444,7 @@ function resolve(
444 }
445 }
446
456 - partialState = Component.getDerivedStateFromProps.call(
447 + let partialState = Component.getDerivedStateFromProps.call(
448 null,
449 element.props,
450 inst.state,
@@ -502,7 +493,7 @@ function resolve(
493 if (inst == null || inst.render == null) {
494 child = inst;
495 validateRenderResult(child, Component);
505 - continue;
496 + return;
497 }
498 }
499
@@ -510,7 +501,7 @@ function resolve(
501 inst.context = publicContext;
502 inst.updater = updater;
503
513 - initialState = inst.state;
504 + let initialState = inst.state;
505 if (initialState === undefined) {
506 inst.state = initialState = null;
507 }
@@ -558,19 +549,19 @@ function resolve(
549 inst.UNSAFE_componentWillMount();
550 }
551 if (queue.length) {
561 - oldQueue = queue;
562 - oldReplace = replace;
552 + let oldQueue = queue;
553 + let oldReplace = replace;
554 queue = null;
555 replace = false;
556
557 if (oldReplace && oldQueue.length === 1) {
558 inst.state = oldQueue[0];
559 } else {
569 - nextState = oldReplace ? oldQueue[0] : inst.state;
570 - dontMutate = true;
560 + let nextState = oldReplace ? oldQueue[0] : inst.state;
561 + let dontMutate = true;
562 for (let i = oldReplace ? 1 : 0; i < oldQueue.length; i++) {
572 - partial = oldQueue[i];
573 - partialState =
563 + let partial = oldQueue[i];
564 + let partialState =
565 typeof partial === 'function'
566 ? partial.call(inst, nextState, element.props, publicContext)
567 : partial;
@@ -600,11 +591,12 @@ function resolve(
591 }
592 validateRenderResult(child, Component);
593
594 + let childContext;
595 if (typeof inst.getChildContext === 'function') {
604 - childContextTypes = Component.childContextTypes;
596 + let childContextTypes = Component.childContextTypes;
597 if (typeof childContextTypes === 'object') {
598 childContext = inst.getChildContext();
607 - for (contextKey in childContext) {
599 + for (let contextKey in childContext) {
600 invariant(
601 contextKey in childContextTypes,
602 '%s.getChildContext(): key "%s" is not defined in childContextTypes.',