Skip to content

[Fiber] Treat unwrapping React.lazy more like a use() #34031

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 29, 2025

Conversation

sebmarkbage
Copy link
Collaborator

While we want to get rid of React.lazy's special wrapper type and just use a Promise for the type, we still have the wrapper.

However, this is still conceptually the same as a Usable in that it should be have the same if you use(promise) or render a Promise as a child or type position.

This PR makes it behave like a use() when we unwrap them. We could move to a model where it actually reaches the internal of the Lazy's Promise when it unwraps but for now I leave the lazy API signature intact by just catching the Promise and then "use()" that.

This lets us align on the semantics with use() such as the suspense yield optimization. It also lets us warn or fork based on legacy throw-a-Promise behavior where as React.lazy is not deprecated.

@sebmarkbage sebmarkbage requested a review from acdlite July 29, 2025 03:15
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Jul 29, 2025
// pre-warming
'Foo',
]);
assertLog(['Foo']);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now a Lazy gets to participate in the optimization when it can be immediately resolved in place instead of causing a rerender.

This doesn't have any of the other legacy quirks like infinite pinging concerns since these are unconditionally rendered in the child position and never inside a component render.

@react-sizebot
Copy link

react-sizebot commented Jul 29, 2025

Comparing: 71236c9...8aeaef0

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 = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 530.70 kB 530.04 kB = 93.70 kB 93.63 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 655.25 kB 654.48 kB = 115.40 kB 115.34 kB
facebook-www/ReactDOM-prod.classic.js = 675.13 kB 674.42 kB = 118.75 kB 118.68 kB
facebook-www/ReactDOM-prod.modern.js = 665.56 kB 664.84 kB = 117.12 kB 117.02 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against 8aeaef0

@darthmaim
Copy link

This seems somewhat related to my bug with lazy children in cloneElement:

@sebmarkbage sebmarkbage merged commit 9be531c into facebook:main Jul 29, 2025
241 checks passed
github-actions bot pushed a commit that referenced this pull request Jul 29, 2025
While we want to get rid of React.lazy's special wrapper type and just
use a Promise for the type, we still have the wrapper.

However, this is still conceptually the same as a Usable in that it
should be have the same if you `use(promise)` or render a Promise as a
child or type position.

This PR makes it behave like a `use()` when we unwrap them. We could
move to a model where it actually reaches the internal of the Lazy's
Promise when it unwraps but for now I leave the lazy API signature
intact by just catching the Promise and then "use()" that.

This lets us align on the semantics with `use()` such as the suspense
yield optimization. It also lets us warn or fork based on legacy
throw-a-Promise behavior where as `React.lazy` is not deprecated.

DiffTrain build for [9be531c](9be531c)
github-actions bot pushed a commit that referenced this pull request Jul 29, 2025
While we want to get rid of React.lazy's special wrapper type and just
use a Promise for the type, we still have the wrapper.

However, this is still conceptually the same as a Usable in that it
should be have the same if you `use(promise)` or render a Promise as a
child or type position.

This PR makes it behave like a `use()` when we unwrap them. We could
move to a model where it actually reaches the internal of the Lazy's
Promise when it unwraps but for now I leave the lazy API signature
intact by just catching the Promise and then "use()" that.

This lets us align on the semantics with `use()` such as the suspense
yield optimization. It also lets us warn or fork based on legacy
throw-a-Promise behavior where as `React.lazy` is not deprecated.

DiffTrain build for [9be531c](9be531c)
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.

5 participants