Skip to content

Commit bb6f0c8

Browse files
authored
[Flight] Fix wrong missing key warning when static child is blocked (#34350)
1 parent aad7c66 commit bb6f0c8

File tree

4 files changed

+105
-6
lines changed

4 files changed

+105
-6
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,14 @@ function getTaskName(type: mixed): string {
10741074
}
10751075
}
10761076

1077-
function initializeElement(response: Response, element: any): void {
1077+
function initializeElement(
1078+
response: Response,
1079+
element: any,
1080+
lazyType: null | LazyComponent<
1081+
React$Element<any>,
1082+
SomeChunk<React$Element<any>>,
1083+
>,
1084+
): void {
10781085
if (!__DEV__) {
10791086
return;
10801087
}
@@ -1141,6 +1148,18 @@ function initializeElement(response: Response, element: any): void {
11411148
if (owner !== null) {
11421149
initializeFakeStack(response, owner);
11431150
}
1151+
1152+
// In case the JSX runtime has validated the lazy type as a static child, we
1153+
// need to transfer this information to the element.
1154+
if (
1155+
lazyType &&
1156+
lazyType._store &&
1157+
lazyType._store.validated &&
1158+
!element._store.validated
1159+
) {
1160+
element._store.validated = lazyType._store.validated;
1161+
}
1162+
11441163
// TODO: We should be freezing the element but currently, we might write into
11451164
// _debugInfo later. We could move it into _store which remains mutable.
11461165
Object.freeze(element.props);
@@ -1230,7 +1249,7 @@ function createElement(
12301249
handler.reason,
12311250
);
12321251
if (__DEV__) {
1233-
initializeElement(response, element);
1252+
initializeElement(response, element, null);
12341253
// Conceptually the error happened inside this Element but right before
12351254
// it was rendered. We don't have a client side component to render but
12361255
// we can add some DebugInfo to explain that this was conceptually a
@@ -1258,16 +1277,17 @@ function createElement(
12581277
createBlockedChunk(response);
12591278
handler.value = element;
12601279
handler.chunk = blockedChunk;
1280+
const lazyType = createLazyChunkWrapper(blockedChunk);
12611281
if (__DEV__) {
1262-
/// After we have initialized any blocked references, initialize stack etc.
1263-
const init = initializeElement.bind(null, response, element);
1282+
// After we have initialized any blocked references, initialize stack etc.
1283+
const init = initializeElement.bind(null, response, element, lazyType);
12641284
blockedChunk.then(init, init);
12651285
}
1266-
return createLazyChunkWrapper(blockedChunk);
1286+
return lazyType;
12671287
}
12681288
}
12691289
if (__DEV__) {
1270-
initializeElement(response, element);
1290+
initializeElement(response, element, null);
12711291
}
12721292

12731293
return element;
@@ -1279,6 +1299,7 @@ function createLazyChunkWrapper<T>(
12791299
const lazyType: LazyComponent<T, SomeChunk<T>> = {
12801300
$$typeof: REACT_LAZY_TYPE,
12811301
_payload: chunk,
1302+
_store: {validated: 0},
12821303
_init: readChunk,
12831304
};
12841305
if (__DEV__) {

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2846,4 +2846,64 @@ describe('ReactFlightDOMBrowser', () => {
28462846

28472847
expect(container.innerHTML).toBe('<p>Hi</p>');
28482848
});
2849+
2850+
it('should not have missing key warnings when a static child is blocked on debug info', async () => {
2851+
const ClientComponent = clientExports(function ClientComponent({element}) {
2852+
return (
2853+
<div>
2854+
<span>Hi</span>
2855+
{element}
2856+
</div>
2857+
);
2858+
});
2859+
2860+
let debugReadableStreamController;
2861+
2862+
const debugReadableStream = new ReadableStream({
2863+
start(controller) {
2864+
debugReadableStreamController = controller;
2865+
},
2866+
});
2867+
2868+
const stream = await serverAct(() =>
2869+
ReactServerDOMServer.renderToReadableStream(
2870+
<ClientComponent element={<span>Sebbie</span>} />,
2871+
webpackMap,
2872+
{
2873+
debugChannel: {
2874+
writable: new WritableStream({
2875+
write(chunk) {
2876+
debugReadableStreamController.enqueue(chunk);
2877+
},
2878+
close() {
2879+
debugReadableStreamController.close();
2880+
},
2881+
}),
2882+
},
2883+
},
2884+
),
2885+
);
2886+
2887+
function ClientRoot({response}) {
2888+
return use(response);
2889+
}
2890+
2891+
const response = ReactServerDOMClient.createFromReadableStream(stream, {
2892+
debugChannel: {readable: createDelayedStream(debugReadableStream)},
2893+
});
2894+
2895+
const container = document.createElement('div');
2896+
const root = ReactDOMClient.createRoot(container);
2897+
2898+
await act(() => {
2899+
root.render(<ClientRoot response={response} />);
2900+
});
2901+
2902+
// Wait for the debug info to be processed.
2903+
await act(() => {});
2904+
2905+
expect(container.innerHTML).toBe(
2906+
'<div><span>Hi</span><span>Sebbie</span></div>',
2907+
);
2908+
});
28492909
});

packages/react/src/ReactLazy.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export type LazyComponent<T, P> = {
6060
_payload: P,
6161
_init: (payload: P) => T,
6262
_debugInfo?: null | ReactDebugInfo,
63+
// __DEV__
64+
_store?: {validated: 0 | 1 | 2, ...}, // 0: not validated, 1: validated, 2: force fail
6365
};
6466

6567
function lazyInitializer<T>(payload: Payload<T>): T {

packages/react/src/jsx/ReactJSXElement.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,14 @@ function validateChildKeys(node) {
804804
if (node._store) {
805805
node._store.validated = 1;
806806
}
807+
} else if (isLazyType(node)) {
808+
if (node._payload.status === 'fulfilled') {
809+
if (isValidElement(node._payload.value) && node._payload.value._store) {
810+
node._payload.value._store.validated = 1;
811+
}
812+
} else if (node._store) {
813+
node._store.validated = 1;
814+
}
807815
}
808816
}
809817
}
@@ -822,3 +830,11 @@ export function isValidElement(object) {
822830
object.$$typeof === REACT_ELEMENT_TYPE
823831
);
824832
}
833+
834+
export function isLazyType(object) {
835+
return (
836+
typeof object === 'object' &&
837+
object !== null &&
838+
object.$$typeof === REACT_LAZY_TYPE
839+
);
840+
}

0 commit comments

Comments
 (0)