Repository navigation
[Flight] Transfer key validation of lazy nodes when they are unwrapped - #37258
Merged
unstubbable merged 4 commits intoAug 10, 2026
Merged
unstubbable merged 4 commits into
unstubbable merged 4 commits into
Conversation
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
|
Comparing: 2042572...48685c8 Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: Expand to show
|
unstubbable
marked this pull request as ready for review
August 10, 2026 12:29
eps1lon
approved these changes
Aug 10, 2026
| lazyType._store = {validated: validated}; | ||
| const store = {validated: validated}; | ||
| lazyType._store = store; | ||
| lazyType._init = (payload: SomeChunk<T>) => { |
Collaborator
There was a problem hiding this comment.
Separate function + bind?
| const store = {validated: validated}; | ||
| lazyType._store = store; | ||
| lazyType._init = (payload: SomeChunk<T>) => { | ||
| const value: any = readChunk(payload); |
Collaborator
There was a problem hiding this comment.
Suggested change
| const value: any = readChunk(payload); | |
| const value: T = readChunk(payload); |
?
eps1lon
approved these changes
Aug 10, 2026
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
initializeElementperformed 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
_initfunction,getTaskNamecan 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 asReact.Children.map, would need its own copy of the same logic, as would the transfer that Flight already performed ininitializeElement. 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