Skip to content

Commit f11a9c1

Browse files
bvaughnacdlite
authored andcommitted
State update bug in concurrent mode (#14698)
* State update bug in concurrent mode * Fix bug introduced by double-rendering Functions using hooks
1 parent e679a4b commit f11a9c1

File tree

2 files changed

+106
-3
lines changed

2 files changed

+106
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ function updateForwardRef(
257257
) {
258258
// Only double-render components with Hooks
259259
if (workInProgress.memoizedState !== null) {
260-
renderWithHooks(
260+
nextChildren = renderWithHooks(
261261
current,
262262
workInProgress,
263263
render,
@@ -567,7 +567,7 @@ function updateFunctionComponent(
567567
) {
568568
// Only double-render components with Hooks
569569
if (workInProgress.memoizedState !== null) {
570-
renderWithHooks(
570+
nextChildren = renderWithHooks(
571571
current,
572572
workInProgress,
573573
Component,
@@ -1252,7 +1252,7 @@ function mountIndeterminateComponent(
12521252
) {
12531253
// Only double-render components with Hooks
12541254
if (workInProgress.memoizedState !== null) {
1255-
renderWithHooks(
1255+
value = renderWithHooks(
12561256
null,
12571257
workInProgress,
12581258
Component,

0 commit comments

Comments
 (0)