Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test case for #16359 #16371

Merged
merged 1 commit into from Aug 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Expand Up @@ -2847,6 +2847,10 @@ function beginWork(
renderExpirationTime,
);
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
// consumer produces a changed value, it will set this to true. Otherwise,
// the component will assume the children have not changed and bail out.
didReceiveUpdate = false;
}
} else {
Expand Down
Expand Up @@ -2138,6 +2138,66 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(ReactNoop).toMatchRenderedOutput('2');
});

// Regression test. Covers a case where an internal state variable
// (`didReceiveUpdate`) is not reset properly.
it('state bail out edge case (#16359)', async () => {
let setCounterA;
let setCounterB;

function CounterA() {
const [counter, setCounter] = useState(0);
setCounterA = setCounter;
Scheduler.unstable_yieldValue('Render A: ' + counter);
useEffect(() => {
Scheduler.unstable_yieldValue('Commit A: ' + counter);
});
return counter;
}

function CounterB() {
const [counter, setCounter] = useState(0);
setCounterB = setCounter;
Scheduler.unstable_yieldValue('Render B: ' + counter);
useEffect(() => {
Scheduler.unstable_yieldValue('Commit B: ' + counter);
});
return counter;
}

const root = ReactNoop.createRoot(null);
await ReactNoop.act(async () => {
root.render(
<>
<CounterA />
<CounterB />
</>,
);
});
expect(Scheduler).toHaveYielded([
'Render A: 0',
'Render B: 0',
'Commit A: 0',
'Commit B: 0',
]);

await ReactNoop.act(async () => {
setCounterA(1);

// In the same batch, update B twice. To trigger the condition we're
// testing, the first update is necessary to bypass the early
// bailout optimization.
setCounterB(1);
setCounterB(0);
});
expect(Scheduler).toHaveYielded([
'Render A: 1',
'Render B: 0',
'Commit A: 1',
// B should not fire an effect because the update bailed out
// 'Commit B: 0',
]);
});

it('should update latest rendered reducer when a preceding state receives a render phase update', () => {
// Similar to previous test, except using a preceding render phase update
// instead of new props.
Expand Down