From 769b13e70cede7f9925f5d42d90afec6e83eb589 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 23 Nov 2020 02:18:20 +0000 Subject: [PATCH 1/2] Failing test for Client reconciliation --- .../src/__tests__/ReactFlightDOM-test.js | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/packages/react-transport-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-transport-dom-webpack/src/__tests__/ReactFlightDOM-test.js index d8568c661c2af..f621a212f19d6 100644 --- a/packages/react-transport-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-transport-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -438,4 +438,92 @@ describe('ReactFlightDOM', () => { '

Game over

', // TODO: should not have message in prod. ); }); + + // @gate experimental + it('should preserve state of client components on refetch', async () => { + const {Suspense} = React; + + // Client + + function Page({response}) { + return response.readRoot(); + } + + function Input() { + return ; + } + + const InputClient = moduleReference(Input); + + // Server + + function App({color}) { + // Verify both DOM and Client children. + return ( +
+ + +
+ ); + } + + const container = document.createElement('div'); + const root = ReactDOM.unstable_createRoot(container); + + const stream1 = getTestStream(); + ReactTransportDOMServer.pipeToNodeWritable( + , + stream1.writable, + webpackMap, + ); + const response1 = ReactTransportDOMClient.createFromReadableStream( + stream1.readable, + ); + await act(async () => { + root.render( + (loading)

}> + +
, + ); + }); + expect(container.children.length).toBe(1); + expect(container.children[0].tagName).toBe('DIV'); + expect(container.children[0].style.color).toBe('red'); + + // Change the DOM state for both inputs. + const inputA = container.children[0].children[0]; + expect(inputA.tagName).toBe('INPUT'); + inputA.value = 'hello'; + const inputB = container.children[0].children[1]; + expect(inputB.tagName).toBe('INPUT'); + inputB.value = 'goodbye'; + + const stream2 = getTestStream(); + ReactTransportDOMServer.pipeToNodeWritable( + , + stream2.writable, + webpackMap, + ); + const response2 = ReactTransportDOMClient.createFromReadableStream( + stream2.readable, + ); + await act(async () => { + root.render( + (loading)

}> + +
, + ); + }); + expect(container.children.length).toBe(1); + expect(container.children[0].tagName).toBe('DIV'); + expect(container.children[0].style.color).toBe('blue'); + + // Verify we didn't destroy the DOM for either input. + expect(inputA === container.children[0].children[0]).toBe(true); + expect(inputA.tagName).toBe('INPUT'); + expect(inputA.value).toBe('hello'); + expect(inputB === container.children[0].children[1]).toBe(true); + expect(inputB.tagName).toBe('INPUT'); + expect(inputB.value).toBe('goodbye'); + }); }); From b64032c700be3cf57a90e48c5343181d77818ac8 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 23 Nov 2020 02:53:12 +0000 Subject: [PATCH 2/2] Fix Client components losing state on refetch --- .../react-server/src/ReactFlightServer.js | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 374ce95397f35..bf2ab3708c2d6 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -442,14 +442,19 @@ export function resolveModelToJSON( request.pendingChunks++; const moduleId = request.nextChunkId++; emitModuleChunk(request, moduleId, moduleMetaData); - if (parent[0] === REACT_ELEMENT_TYPE && key === '1') { - // If we're encoding the "type" of an element, we can refer - // to that by a lazy reference instead of directly since React - // knows how to deal with lazy values. This lets us suspend - // on this component rather than its parent until the code has - // loaded. - return serializeByRefID(moduleId); - } + + // Removed because this make lazy "type" different between refetches. + // TODO: add this back? + + // if (parent[0] === REACT_ELEMENT_TYPE && key === '1') { + // // If we're encoding the "type" of an element, we can refer + // // to that by a lazy reference instead of directly since React + // // knows how to deal with lazy values. This lets us suspend + // // on this component rather than its parent until the code has + // // loaded. + // return serializeByRefID(moduleId); + // } + return serializeByValueID(moduleId); } catch (x) { request.pendingChunks++;