@samitouri / QOS-React-2 / commits / d830cd9984

[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 });