Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 21d6395

Browse files
authored
Add test case for facebook#16359 (facebook#16371)
1 parent a29adc9 commit 21d6395

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2937,6 +2937,10 @@ function beginWork(
29372937
renderExpirationTime,
29382938
);
29392939
} else {
2940+
// An update was scheduled on this fiber, but there are no new props
2941+
// nor legacy context. Set this to false. If an update queue or context
2942+
// consumer produces a changed value, it will set this to true. Otherwise,
2943+
// the component will assume the children have not changed and bail out.
29402944
didReceiveUpdate = false;
29412945
}
29422946
} else {

packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2138,6 +2138,66 @@ describe('ReactHooksWithNoopRenderer', () => {
21382138
expect(ReactNoop).toMatchRenderedOutput('2');
21392139
});
21402140

2141+
// Regression test. Covers a case where an internal state variable
2142+
// (`didReceiveUpdate`) is not reset properly.
2143+
it('state bail out edge case (#16359)', async () => {
2144+
let setCounterA;
2145+
let setCounterB;
2146+
2147+
function CounterA() {
2148+
const [counter, setCounter] = useState(0);
2149+
setCounterA = setCounter;
2150+
Scheduler.unstable_yieldValue('Render A: ' + counter);
2151+
useEffect(() => {
2152+
Scheduler.unstable_yieldValue('Commit A: ' + counter);
2153+
});
2154+
return counter;
2155+
}
2156+
2157+
function CounterB() {
2158+
const [counter, setCounter] = useState(0);
2159+
setCounterB = setCounter;
2160+
Scheduler.unstable_yieldValue('Render B: ' + counter);
2161+
useEffect(() => {
2162+
Scheduler.unstable_yieldValue('Commit B: ' + counter);
2163+
});
2164+
return counter;
2165+
}
2166+
2167+
const root = ReactNoop.createRoot(null);
2168+
await ReactNoop.act(async () => {
2169+
root.render(
2170+
<>
2171+
<CounterA />
2172+
<CounterB />
2173+
</>,
2174+
);
2175+
});
2176+
expect(Scheduler).toHaveYielded([
2177+
'Render A: 0',
2178+
'Render B: 0',
2179+
'Commit A: 0',
2180+
'Commit B: 0',
2181+
]);
2182+
2183+
await ReactNoop.act(async () => {
2184+
setCounterA(1);
2185+
2186+
// In the same batch, update B twice. To trigger the condition we're
2187+
// testing, the first update is necessary to bypass the early
2188+
// bailout optimization.
2189+
setCounterB(1);
2190+
setCounterB(0);
2191+
});
2192+
expect(Scheduler).toHaveYielded([
2193+
'Render A: 1',
2194+
'Render B: 0',
2195+
'Commit A: 1',
2196+
// B should not fire an effect because the update bailed out
2197+
// 'Commit B: 0',
2198+
]);
2199+
});
2200+
21412201
it('should update latest rendered reducer when a preceding state receives a render phase update', () => {
21422202
// Similar to previous test, except using a preceding render phase update
21432203
// instead of new props.

0 commit comments

Comments
 (0)