镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

[Flight] Transfer key validation of lazy nodes when they are unwrapped - #37258

Merged
unstubbable merged 4 commits into
react:mainfrom
unstubbable:fix-missing-key-warnings-in-flight-client
Aug 10, 2026
Merged

unstubbable merged 4 commits into
react:mainfrom
unstubbable:fix-missing-key-warnings-in-flight-client

Conversation

@unstubbable

Copy link
Copy Markdown
Collaborator

When Flight outlines a value into its own row, the client wraps the reference in a lazy node at the reference site, while the value itself is created later, when the target row is initialized. The JSX runtime can therefore only mark the lazy node as a validated static child, and that mark never reached the element, which produced a false-positive missing key warning for a child that isn't in an array at all. Whether the warning appeared depended on whether a preceding prop was large enough to push the element past the outlining threshold.

The transfer now happens when the lazy node is unwrapped, which is the first point at which both the mark and the value exist. Unwrapping is also the only way any consumer reaches the value, so a single transfer covers all of them, and a value that is itself a lazy node forwards the validation one step further. That nested case shows up when an outlined row contains an element that is blocked on debug info.

This subsumes the transfer that initializeElement performed for blocked elements, because those are read through the same lazy node, so that special case is gone.

Because the lazy nodes created for chunks no longer share a single _init function, getTaskName can no longer use it to recognize them. It checks that the payload is a chunk instead, which describes the same set of lazy nodes without depending on which function unwraps them.

Fixing this in Fiber instead, as #37246 proposed, would only cover the reconciler's own warnOnInvalidKey. Anything else that unwraps a lazy node and then consults _store.validated, such as React.Children.map, would need its own copy of the same logic, as would the transfer that Flight already performed in initializeElement. It would also leave the nested lazy node case warning. Since Flight is what turns a plain element into a lazy node to begin with, it should also be what hides that again.

fixes #37240
closes #37246

When Flight outlines a value into its own row, the client wraps the
reference in a lazy node at the reference site, while the value itself
is created later, when the target row is initialized. The JSX runtime
can therefore only mark the lazy node as a validated static child, and
that mark never reached the element, which produced a false-positive
missing key warning for a child that isn't in an array at all. Whether
the warning appeared depended on whether a preceding prop was large
enough to push the element past the outlining threshold.

The transfer now happens when the lazy node is unwrapped, which is the
first point at which both the mark and the value exist. Unwrapping is
also the only way any consumer reaches the value, so a single transfer
covers all of them, and a value that is itself a lazy node forwards the
validation one step further. That nested case shows up when an outlined
row contains an element that is blocked on debug info.

This subsumes the transfer that `initializeElement` performed for
blocked elements, because those are read through the same lazy node, so
that special case is gone.

Because the lazy nodes created for chunks no longer share a single
`_init` function, `getTaskName` can no longer use it to recognize them.
It checks that the payload is a chunk instead, which describes the same
set of lazy nodes without depending on which function unwraps them.

Fixing this in Fiber instead, as react#37246 proposed, would only cover the
reconciler's own `warnOnInvalidKey`. Anything else that unwraps a lazy
node and then consults `_store.validated`, such as `React.Children.map`,
would need its own copy of the same logic, as would the transfer that
Flight already performed in `initializeElement`. It would also leave the
nested lazy node case warning. Since Flight is what turns a plain
element into a lazy node to begin with, it should also be what hides
that again.

fixes react#37240
closes react#37246
@meta-cla meta-cla Bot added the CLA Signed label Aug 10, 2026
@github-actions github-actions Bot added the React Core Team Opened by a member of the React Core Team label Aug 10, 2026
@react-sizebot

react-sizebot commented Aug 10, 2026 •

Copy link
Copy Markdown

Comparing: 2042572...48685c8

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 7.45 kB 7.45 kB = 2.00 kB 2.00 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 617.53 kB 617.53 kB = 109.41 kB 109.41 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 7.46 kB 7.46 kB = 2.00 kB 2.00 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 688.75 kB 688.75 kB = 120.85 kB 120.85 kB
facebook-www/ReactDOM-prod.classic.js = 709.04 kB 709.04 kB = 124.37 kB 124.37 kB
facebook-www/ReactDOM-prod.modern.js = 699.35 kB 699.35 kB = 122.79 kB 122.79 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.40% 243.71 kB 244.68 kB +0.51% 54.52 kB 54.80 kB
oss-stable/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.40% 243.74 kB 244.71 kB +0.51% 54.55 kB 54.82 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.40% 244.68 kB 245.65 kB +0.52% 54.65 kB 54.93 kB
oss-stable-semver/react-client/cjs/react-client-flight.development.js +0.20% 184.62 kB 184.99 kB +0.19% 31.98 kB 32.04 kB
oss-stable/react-client/cjs/react-client-flight.development.js +0.20% 184.64 kB 185.01 kB +0.18% 32.01 kB 32.07 kB

Generated by 🚫 dangerJS against 48685c8

@unstubbable
unstubbable marked this pull request as ready for review August 10, 2026 12:29
@unstubbable
unstubbable requested a review from eps1lon August 10, 2026 12:29
lazyType._store = {validated: validated};
const store = {validated: validated};
lazyType._store = store;
lazyType._init = (payload: SomeChunk<T>) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate function + bind?

const store = {validated: validated};
lazyType._store = store;
lazyType._init = (payload: SomeChunk<T>) => {
const value: any = readChunk(payload);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const value: any = readChunk(payload);
const value: T = readChunk(payload);

?

@unstubbable
unstubbable requested a review from eps1lon August 10, 2026 13:05
@unstubbable
unstubbable merged commit 8366f33 into react:main Aug 10, 2026
238 checks passed
github-actions Bot pushed a commit that referenced this pull request Aug 10, 2026
@SidiEyel

Copy link
Copy Markdown
Contributor

Thanks — the unwrap site is clearly the right layer, and it explains why the #34350 transfer could be subsumed too. Good to see this fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: False-positive missing key warning when Flight outlines a static child

4 participants