[Blocks] Fix stale data on updates (#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
Dan Abramov committed
May 4, 2020 at 01:43 UTC
d830cd998490c12164da6a9e1ddff56daff80030
3 files changed
+91
packages/react-reconciler/src/ReactFiber.new.js
+4
@@ -282,6 +282,8 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
282
current.alternate = workInProgress;
283
} else {
284
workInProgress.pendingProps = pendingProps;
285
+ // Needed because Blocks store data on type.
286
+ workInProgress.type = current.type;
287
288
// We already have an alternate.
289
// Reset the effect tag.
@@ -415,6 +417,8 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
417
workInProgress.memoizedProps = current.memoizedProps;
418
workInProgress.memoizedState = current.memoizedState;
419
workInProgress.updateQueue = current.updateQueue;
420
+ // Needed because Blocks store data on type.
421
+ workInProgress.type = current.type;
422
423
// Clone the dependencies object. This is mutated during the render phase, so
424
// it cannot be shared with the current fiber.
packages/react-reconciler/src/ReactFiber.old.js
+4
@@ -277,6 +277,8 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
277
current.alternate = workInProgress;
278
} else {
279
workInProgress.pendingProps = pendingProps;
280
+ // Needed because Blocks store data on type.
281
+ workInProgress.type = current.type;
282
283
// We already have an alternate.
284
// Reset the effect tag.
@@ -413,6 +415,8 @@ export function resetWorkInProgress(
415
workInProgress.memoizedProps = current.memoizedProps;
416
workInProgress.memoizedState = current.memoizedState;
417
workInProgress.updateQueue = current.updateQueue;
418
+ // Needed because Blocks store data on type.
419
+ workInProgress.type = current.type;
420
421
// Clone the dependencies object. This is mutated during the render phase, so
422
// it cannot be shared with the current fiber.
packages/react-reconciler/src/__tests__/ReactBlocks-test.js
+83
@@ -257,4 +257,87 @@ describe('ReactBlocks', () => {
257
</>,
258
);
259
});
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
+ });
343
});