Add test case for #16359 (#16371)
Andrew Clark committed
Aug 12, 2019 at 19:00 UTC
21d6395a141f1e725cdf707b606b05f079f48b92
2 files changed
+64
packages/react-reconciler/src/ReactFiberBeginWork.js
+4
@@ -2937,6 +2937,10 @@ function beginWork(
2937
renderExpirationTime,
2938
);
2939
} 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.
2944
didReceiveUpdate = false;
2945
}
2946
} else {
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
+60
@@ -2138,6 +2138,66 @@ describe('ReactHooksWithNoopRenderer', () => {
2138
expect(ReactNoop).toMatchRenderedOutput('2');
2139
});
2140
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
+
2201
it('should update latest rendered reducer when a preceding state receives a render phase update', () => {
2202
// Similar to previous test, except using a preceding render phase update
2203
// instead of new props.