Skip to content

Commit d830cd9

Browse files
authored
[Blocks] Fix stale data on updates (facebook#18810)
* [Blocks] Failing test for nested load * Simplify the test * Add a similar test that fails in PROD * Copy .type when cloning work in progress
1 parent 64d4b84 commit d830cd9

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

packages/react-reconciler/src/ReactFiber.new.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
282282
current.alternate = workInProgress;
283283
} else {
284284
workInProgress.pendingProps = pendingProps;
285+
// Needed because Blocks store data on type.
286+
workInProgress.type = current.type;
285287

286288
// We already have an alternate.
287289
// Reset the effect tag.
@@ -415,6 +417,8 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
415417
workInProgress.memoizedProps = current.memoizedProps;
416418
workInProgress.memoizedState = current.memoizedState;
417419
workInProgress.updateQueue = current.updateQueue;
420+
// Needed because Blocks store data on type.
421+
workInProgress.type = current.type;
418422

419423
// Clone the dependencies object. This is mutated during the render phase, so
420424
// it cannot be shared with the current fiber.

packages/react-reconciler/src/ReactFiber.old.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
277277
current.alternate = workInProgress;
278278
} else {
279279
workInProgress.pendingProps = pendingProps;
280+
// Needed because Blocks store data on type.
281+
workInProgress.type = current.type;
280282

281283
// We already have an alternate.
282284
// Reset the effect tag.
@@ -413,6 +415,8 @@ export function resetWorkInProgress(
413415
workInProgress.memoizedProps = current.memoizedProps;
414416
workInProgress.memoizedState = current.memoizedState;
415417
workInProgress.updateQueue = current.updateQueue;
418+
// Needed because Blocks store data on type.
419+
workInProgress.type = current.type;
416420

417421
// Clone the dependencies object. This is mutated during the render phase, so
418422
// it cannot be shared with the current fiber.

packages/react-reconciler/src/__tests__/ReactBlocks-test.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,87 @@ describe('ReactBlocks', () => {
257257
</>,
258258
);
259259
});
260+
261+
// Regression test.
262+
// @gate experimental
263+
it('does not render stale data after ping', async () => {
264+
function Child() {
265+
return <span>Name: {readString('Sebastian')}</span>;
266+
}
267+
268+
const loadParent = block(
269+
function Parent(props, data) {
270+
return (
271+
<Suspense fallback="Loading...">
272+
{data.name ? <Child /> : <span>Empty</span>}
273+
</Suspense>
274+
);
275+
},
276+
function load(name) {
277+
return {name};
278+
},
279+
);
280+
281+
function App({Page}) {
282+
return <Page />;
283+
}
284+
285+
await ReactNoop.act(async () => {
286+
ReactNoop.render(<App Page={loadParent(null)} />);
287+
});
288+
expect(ReactNoop).toMatchRenderedOutput(<span>Empty</span>);
289+
290+
await ReactNoop.act(async () => {
291+
ReactNoop.render(<App Page={loadParent('Sebastian')} />);
292+
});
293+
await ReactNoop.act(async () => {
294+
jest.advanceTimersByTime(1000);
295+
});
296+
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
297+
});
298+
299+
// Regression test.
300+
// @gate experimental
301+
it('does not render stale data after ping and setState', async () => {
302+
function Child() {
303+
return <span>Name: {readString('Sebastian')}</span>;
304+
}
305+
306+
let _setSuspend;
307+
const loadParent = block(
308+
function Parent(props, data) {
309+
const [suspend, setSuspend] = useState(true);
310+
_setSuspend = setSuspend;
311+
if (!suspend) {
312+
return <span>{data.name}</span>;
313+
}
314+
return (
315+
<Suspense fallback="Loading...">
316+
{data.name ? <Child /> : <span>Empty</span>}
317+
</Suspense>
318+
);
319+
},
320+
function load(name) {
321+
return {name};
322+
},
323+
);
324+
325+
function App({Page}) {
326+
return <Page />;
327+
}
328+
329+
await ReactNoop.act(async () => {
330+
ReactNoop.render(<App Page={loadParent(null)} />);
331+
});
332+
expect(ReactNoop).toMatchRenderedOutput(<span>Empty</span>);
333+
334+
await ReactNoop.act(async () => {
335+
ReactNoop.render(<App Page={loadParent('Sebastian')} />);
336+
});
337+
await ReactNoop.act(async () => {
338+
_setSuspend(false);
339+
jest.advanceTimersByTime(1000);
340+
});
341+
expect(ReactNoop).toMatchRenderedOutput(<span>Sebastian</span>);
342+
});
260343
});

0 commit comments

Comments
 (0)