@samitouri / QOS-React-2 / commits / f11a9c1cb0

State update bug in concurrent mode (#14698)

* State update bug in concurrent mode * Fix bug introduced by double-rendering Functions using hooks

Brian Vaughn committed Jan 24, 2019 at 16:10 UTC f11a9c1cb06514e40a50503e0f9bd26941251e06
2 files changed +106 -3
packages/react-dom/src/__tests__/ReactDOMHooks-test.js new
+103
@@ -0,0 +1,103 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +let React;
13 +let ReactDOM;
14 +
15 +describe('ReactDOMSuspensePlaceholder', () => {
16 + let container;
17 +
18 + beforeEach(() => {
19 + jest.resetModules();
20 +
21 + React = require('react');
22 + ReactDOM = require('react-dom');
23 +
24 + container = document.createElement('div');
25 + document.body.appendChild(container);
26 + });
27 +
28 + afterEach(() => {
29 + document.body.removeChild(container);
30 + });
31 +
32 + it('should not bail out when an update is scheduled from within an event handler', () => {
33 + const {createRef, useCallback, useState} = React;
34 +
35 + const Example = ({inputRef, labelRef}) => {
36 + const [text, setText] = useState('');
37 + const handleInput = useCallback(event => {
38 + setText(event.target.value);
39 + });
40 +
41 + return (
42 + <React.Fragment>
43 + <input ref={inputRef} onInput={handleInput} />
44 + <label ref={labelRef}>{text}</label>
45 + </React.Fragment>
46 + );
47 + };
48 +
49 + const inputRef = createRef();
50 + const labelRef = createRef();
51 +
52 + ReactDOM.render(
53 + <Example inputRef={inputRef} labelRef={labelRef} />,
54 + container,
55 + );
56 +
57 + inputRef.current.value = 'abc';
58 + inputRef.current.dispatchEvent(
59 + new Event('input', {bubbles: true, cancelable: true}),
60 + );
61 +
62 + expect(labelRef.current.innerHTML).toBe('abc');
63 + });
64 +
65 + it('should not bail out when an update is scheduled from within an event handler in ConcurrentMode', () => {
66 + const {createRef, useCallback, useState} = React;
67 +
68 + const Example = ({inputRef, labelRef}) => {
69 + const [text, setText] = useState('');
70 + const handleInput = useCallback(event => {
71 + setText(event.target.value);
72 + });
73 +
74 + return (
75 + <React.Fragment>
76 + <input ref={inputRef} onInput={handleInput} />
77 + <label ref={labelRef}>{text}</label>
78 + </React.Fragment>
79 + );
80 + };
81 +
82 + const inputRef = createRef();
83 + const labelRef = createRef();
84 +
85 + const root = ReactDOM.unstable_createRoot(container);
86 + root.render(
87 + <React.unstable_ConcurrentMode>
88 + <Example inputRef={inputRef} labelRef={labelRef} />
89 + </React.unstable_ConcurrentMode>,
90 + );
91 +
92 + jest.runAllTimers();
93 +
94 + inputRef.current.value = 'abc';
95 + inputRef.current.dispatchEvent(
96 + new Event('input', {bubbles: true, cancelable: true}),
97 + );
98 +
99 + jest.runAllTimers();
100 +
101 + expect(labelRef.current.innerHTML).toBe('abc');
102 + });
103 +});
packages/react-reconciler/src/ReactFiberBeginWork.js
+3 -3
@@ -257,7 +257,7 @@ function updateForwardRef(
257 ) {
258 // Only double-render components with Hooks
259 if (workInProgress.memoizedState !== null) {
260 - renderWithHooks(
260 + nextChildren = renderWithHooks(
261 current,
262 workInProgress,
263 render,
@@ -567,7 +567,7 @@ function updateFunctionComponent(
567 ) {
568 // Only double-render components with Hooks
569 if (workInProgress.memoizedState !== null) {
570 - renderWithHooks(
570 + nextChildren = renderWithHooks(
571 current,
572 workInProgress,
573 Component,
@@ -1252,7 +1252,7 @@ function mountIndeterminateComponent(
1252 ) {
1253 // Only double-render components with Hooks
1254 if (workInProgress.memoizedState !== null) {
1255 - renderWithHooks(
1255 + value = renderWithHooks(
1256 null,
1257 workInProgress,
1258 Component,