Skip to content
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

Suspend Thenable/Lazy if it's used in React.Children and unwrap #28284

Merged
merged 1 commit into from
Feb 12, 2024

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented Feb 9, 2024

This pains me because React.Children is really already pseudo-deprecated.

React.Children takes any children that React.Node takes. We now support Lazy and Thenable in this position elsewhere, but it errors in React.Children.

This becomes an issue with async Server Components which can resolve into a Lazy and in the future Lazy will just become Thenables. Which causes this to error.

There are a few different semantics we could have:

  1. Error like we already do (Throw a better error when Lazy/Promise is used in React.Children #28280). React.Children is about introspecting children. It was always sketchy because you can't introspect inside an abstraction anyway. With Server Components we fold away the components so you can actually introspect inside of them kind of but what they do is an implementation detail and you should be able to turn it into a Client Component at any point. The type of an Element passing the boundary actually reduces to React.Node.
  2. Suspend and unwrap the Node (this PR). If we assume that Children is called inside of render, then throwing a Promise if it's not already loaded or unwrapping would treat it as if it wasn't there. Just like if you rendered it in React. This lets you introspect what's inside which isn't really something you should be able to do. This isn't compatible with deprecating throwing-a-Promise and enable static compilation like use() does. We'd have to deprecate React.Children before doing that which we might anyway.
  3. Wrap in a Fragment. If a Server Component was instead a Client Component, you couldn't introspect through it anyway. Another alternative might be to let it pass through but then it wouldn't be given a flat key. We could also wrap it in a Fragment that is keyed. That way you're always seeing an element. The issue with this solution is that it wouldn't see the key of the Server Component since that gets forwarded to the child that is yet to resolve. The nice thing about that strategy is it doesn't depend on throw-a-Promise but it might not be keyed correctly when things move.

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Feb 9, 2024
@@ -212,19 +212,19 @@ export function trackUsedThenable<T>(
}
},
);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@acdlite I believe that the end curly was not in the right position because if we hit this path with an uninitialized Flight object, we should be able to synchronously return the value without suspending once.

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberThenable.js#L164-L169

throw rejectedError;
}
// Check one more time in case the thenable resolved synchronously.
switch (thenable.status) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not that it's an issue per se, but why did this move into the outer block? Because the .then(noop) triggers a lazy initializer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@react-sizebot
Copy link

react-sizebot commented Feb 9, 2024

Comparing: ba5e6a8...adb0bef

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.min.js = 176.64 kB 176.64 kB = 55.02 kB 55.02 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 178.63 kB 178.63 kB = 55.60 kB 55.59 kB
facebook-www/ReactDOM-prod.classic.js = 591.73 kB 591.71 kB = 104.44 kB 104.44 kB
facebook-www/ReactDOM-prod.modern.js = 575.51 kB 575.49 kB = 101.53 kB 101.53 kB
facebook-www/ReactServer-prod.modern.js +8.41% 15.07 kB 16.34 kB +5.53% 4.01 kB 4.23 kB
oss-stable-semver/react/cjs/react.react-server.production.js +7.57% 30.21 kB 32.49 kB +6.17% 8.97 kB 9.52 kB
oss-stable/react/cjs/react.react-server.production.js +7.56% 30.23 kB 32.52 kB +6.14% 8.99 kB 9.55 kB
oss-experimental/react/cjs/react.react-server.production.js +6.29% 36.32 kB 38.61 kB +5.27% 10.64 kB 11.20 kB
oss-stable-semver/react/cjs/react.react-server.production.min.js +6.11% 7.07 kB 7.51 kB +4.31% 3.00 kB 3.13 kB
oss-stable/react/cjs/react.react-server.production.min.js +6.09% 7.10 kB 7.53 kB +4.20% 3.02 kB 3.15 kB
oss-stable-semver/react/cjs/react.production.js +5.71% 35.34 kB 37.35 kB +5.08% 9.77 kB 10.26 kB
oss-stable/react/cjs/react.production.js +5.71% 35.36 kB 37.38 kB +5.01% 9.79 kB 10.29 kB
facebook-react-native/react/cjs/React-prod.js +5.33% 18.15 kB 19.11 kB +3.09% 4.73 kB 4.87 kB
oss-experimental/react/cjs/react.production.js +5.31% 37.97 kB 39.99 kB +4.64% 10.44 kB 10.92 kB
facebook-react-native/react/cjs/React-profiling.js +5.29% 18.29 kB 19.25 kB +3.24% 4.72 kB 4.87 kB
facebook-www/React-prod.modern.js +5.17% 18.83 kB 19.80 kB +3.24% 4.81 kB 4.97 kB
facebook-www/React-prod.classic.js +5.09% 19.12 kB 20.10 kB +3.15% 4.90 kB 5.05 kB
facebook-www/React-profiling.modern.js +5.05% 19.26 kB 20.24 kB +3.17% 4.89 kB 5.05 kB
facebook-www/React-profiling.classic.js +4.98% 19.56 kB 20.53 kB +2.99% 4.98 kB 5.13 kB
oss-experimental/react/cjs/react.react-server.production.min.js +4.77% 9.12 kB 9.56 kB +3.38% 3.70 kB 3.82 kB
oss-stable-semver/react/cjs/react.react-server.development.js +2.58% 78.08 kB 80.09 kB +2.24% 21.52 kB 22.00 kB
oss-stable/react/cjs/react.react-server.development.js +2.58% 78.10 kB 80.12 kB +2.22% 21.55 kB 22.03 kB
oss-experimental/react/cjs/react.react-server.development.js +2.38% 84.53 kB 86.55 kB +2.04% 23.41 kB 23.88 kB
facebook-www/ReactServer-dev.modern.js +2.06% 108.86 kB 111.10 kB +1.96% 26.40 kB 26.91 kB
test_utils/ReactAllWarnings.js Deleted 67.02 kB 0.00 kB Deleted 16.42 kB 0.00 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
facebook-www/ReactServer-prod.modern.js +8.41% 15.07 kB 16.34 kB +5.53% 4.01 kB 4.23 kB
oss-stable-semver/react/cjs/react.react-server.production.js +7.57% 30.21 kB 32.49 kB +6.17% 8.97 kB 9.52 kB
oss-stable/react/cjs/react.react-server.production.js +7.56% 30.23 kB 32.52 kB +6.14% 8.99 kB 9.55 kB
oss-experimental/react/cjs/react.react-server.production.js +6.29% 36.32 kB 38.61 kB +5.27% 10.64 kB 11.20 kB
oss-stable-semver/react/cjs/react.react-server.production.min.js +6.11% 7.07 kB 7.51 kB +4.31% 3.00 kB 3.13 kB
oss-stable/react/cjs/react.react-server.production.min.js +6.09% 7.10 kB 7.53 kB +4.20% 3.02 kB 3.15 kB
oss-stable-semver/react/cjs/react.production.js +5.71% 35.34 kB 37.35 kB +5.08% 9.77 kB 10.26 kB
oss-stable/react/cjs/react.production.js +5.71% 35.36 kB 37.38 kB +5.01% 9.79 kB 10.29 kB
facebook-react-native/react/cjs/React-prod.js +5.33% 18.15 kB 19.11 kB +3.09% 4.73 kB 4.87 kB
oss-experimental/react/cjs/react.production.js +5.31% 37.97 kB 39.99 kB +4.64% 10.44 kB 10.92 kB
facebook-react-native/react/cjs/React-profiling.js +5.29% 18.29 kB 19.25 kB +3.24% 4.72 kB 4.87 kB
facebook-www/React-prod.modern.js +5.17% 18.83 kB 19.80 kB +3.24% 4.81 kB 4.97 kB
facebook-www/React-prod.classic.js +5.09% 19.12 kB 20.10 kB +3.15% 4.90 kB 5.05 kB
facebook-www/React-profiling.modern.js +5.05% 19.26 kB 20.24 kB +3.17% 4.89 kB 5.05 kB
facebook-www/React-profiling.classic.js +4.98% 19.56 kB 20.53 kB +2.99% 4.98 kB 5.13 kB
oss-experimental/react/cjs/react.react-server.production.min.js +4.77% 9.12 kB 9.56 kB +3.38% 3.70 kB 3.82 kB
oss-stable-semver/react/cjs/react.react-server.development.js +2.58% 78.08 kB 80.09 kB +2.24% 21.52 kB 22.00 kB
oss-stable/react/cjs/react.react-server.development.js +2.58% 78.10 kB 80.12 kB +2.22% 21.55 kB 22.03 kB
oss-experimental/react/cjs/react.react-server.development.js +2.38% 84.53 kB 86.55 kB +2.04% 23.41 kB 23.88 kB
facebook-www/ReactServer-dev.modern.js +2.06% 108.86 kB 111.10 kB +1.96% 26.40 kB 26.91 kB
oss-stable-semver/react/cjs/react.development.js +1.97% 102.34 kB 104.36 kB +1.79% 27.30 kB 27.79 kB
oss-stable/react/cjs/react.development.js +1.97% 102.37 kB 104.39 kB +1.78% 27.33 kB 27.82 kB
oss-experimental/react/cjs/react.development.js +1.92% 104.91 kB 106.92 kB +1.73% 27.97 kB 28.45 kB
facebook-react-native/react/cjs/React-dev.js +1.74% 128.34 kB 130.58 kB +1.61% 31.00 kB 31.50 kB
oss-stable-semver/react/umd/react.development.js +1.70% 125.42 kB 127.55 kB +1.55% 32.01 kB 32.51 kB
oss-stable/react/umd/react.development.js +1.70% 125.44 kB 127.58 kB +1.55% 32.04 kB 32.53 kB
oss-stable-semver/react/cjs/react.production.min.js +1.68% 8.44 kB 8.58 kB +1.40% 3.20 kB 3.25 kB
oss-stable/react/cjs/react.production.min.js +1.68% 8.47 kB 8.61 kB +1.36% 3.23 kB 3.27 kB
oss-experimental/react/umd/react.development.js +1.67% 128.09 kB 130.22 kB +1.52% 32.73 kB 33.23 kB
facebook-www/React-dev.modern.js +1.59% 141.18 kB 143.42 kB +1.39% 34.33 kB 34.81 kB
facebook-www/React-dev.classic.js +1.58% 142.40 kB 144.65 kB +1.36% 34.57 kB 35.04 kB
oss-experimental/react/cjs/react.production.min.js +1.52% 9.40 kB 9.54 kB +1.37% 3.51 kB 3.56 kB
oss-stable-semver/react/umd/react.profiling.min.js +1.17% 12.24 kB 12.38 kB +0.76% 4.74 kB 4.77 kB
oss-stable-semver/react/umd/react.production.min.js +1.17% 12.24 kB 12.38 kB +0.78% 4.74 kB 4.77 kB
oss-stable/react/umd/react.profiling.min.js +1.17% 12.26 kB 12.41 kB +0.74% 4.76 kB 4.79 kB
oss-stable/react/umd/react.production.min.js +1.17% 12.26 kB 12.41 kB +0.74% 4.76 kB 4.79 kB
oss-experimental/react/umd/react.profiling.min.js +1.09% 13.13 kB 13.28 kB +0.98% 5.02 kB 5.07 kB
oss-experimental/react/umd/react.production.min.js +1.09% 13.13 kB 13.28 kB +0.98% 5.02 kB 5.07 kB
test_utils/ReactAllWarnings.js Deleted 67.02 kB 0.00 kB Deleted 16.42 kB 0.00 kB

Generated by 🚫 dangerJS against adb0bef

@sebmarkbage sebmarkbage merged commit 9e7944f into facebook:main Feb 12, 2024
36 checks passed
github-actions bot pushed a commit that referenced this pull request Feb 12, 2024
This pains me because `React.Children` is really already
pseudo-deprecated.

`React.Children` takes any children that `React.Node` takes. We now
support Lazy and Thenable in this position elsewhere, but it errors in
`React.Children`.

This becomes an issue with async Server Components which can resolve
into a Lazy and in the future Lazy will just become Thenables. Which
causes this to error.

There are a few different semantics we could have:

1) Error like we already do (#28280). `React.Children` is about
introspecting children. It was always sketchy because you can't
introspect inside an abstraction anyway. With Server Components we fold
away the components so you can actually introspect inside of them kind
of but what they do is an implementation detail and you should be able
to turn it into a Client Component at any point. The type of an Element
passing the boundary actually reduces to `React.Node`.
2) Suspend and unwrap the Node (this PR). If we assume that Children is
called inside of render, then throwing a Promise if it's not already
loaded or unwrapping would treat it as if it wasn't there. Just like if
you rendered it in React. This lets you introspect what's inside which
isn't really something you should be able to do. This isn't compatible
with deprecating throwing-a-Promise and enable static compilation like
`use()` does. We'd have to deprecate `React.Children` before doing that
which we might anyway.
3) Wrap in a Fragment. If a Server Component was instead a Client
Component, you couldn't introspect through it anyway. Another
alternative might be to let it pass through but then it wouldn't be
given a flat key. We could also wrap it in a Fragment that is keyed.
That way you're always seeing an element. The issue with this solution
is that it wouldn't see the key of the Server Component since that gets
forwarded to the child that is yet to resolve. The nice thing about that
strategy is it doesn't depend on throw-a-Promise but it might not be
keyed correctly when things move.

DiffTrain build for [9e7944f](9e7944f)
huozhi added a commit to vercel/next.js that referenced this pull request Feb 23, 2024
### React upstream changes

- facebook/react#28333
- facebook/react#28334
- facebook/react#28378
- facebook/react#28377
- facebook/react#28376
- facebook/react#28338
- facebook/react#28331
- facebook/react#28336
- facebook/react#28320
- facebook/react#28317
- facebook/react#28375
- facebook/react#28367
- facebook/react#28380
- facebook/react#28368
- facebook/react#28343
- facebook/react#28355
- facebook/react#28374
- facebook/react#28362
- facebook/react#28344
- facebook/react#28339
- facebook/react#28353
- facebook/react#28346
- facebook/react#25790
- facebook/react#28352
- facebook/react#28326
- facebook/react#27688
- facebook/react#28329
- facebook/react#28332
- facebook/react#28340
- facebook/react#28327
- facebook/react#28325
- facebook/react#28324
- facebook/react#28309
- facebook/react#28310
- facebook/react#28307
- facebook/react#28306
- facebook/react#28315
- facebook/react#28318
- facebook/react#28226
- facebook/react#28308
- facebook/react#27563
- facebook/react#28297
- facebook/react#28286
- facebook/react#28284
- facebook/react#28275
- facebook/react#28145
- facebook/react#28301
- facebook/react#28224
- facebook/react#28152
- facebook/react#28296
- facebook/react#28294
- facebook/react#28279
- facebook/react#28273
- facebook/react#28269
- facebook/react#28376
- facebook/react#28338
- facebook/react#28331
- facebook/react#28336
- facebook/react#28320
- facebook/react#28317
- facebook/react#28375
- facebook/react#28367
- facebook/react#28380
- facebook/react#28368
- facebook/react#28343
- facebook/react#28355
- facebook/react#28374
- facebook/react#28362
- facebook/react#28344
- facebook/react#28339
- facebook/react#28353
- facebook/react#28346
- facebook/react#25790
- facebook/react#28352
- facebook/react#28326
- facebook/react#27688
- facebook/react#28329
- facebook/react#28332
- facebook/react#28340
- facebook/react#28327
- facebook/react#28325
- facebook/react#28324
- facebook/react#28309
- facebook/react#28310
- facebook/react#28307
- facebook/react#28306
- facebook/react#28315
- facebook/react#28318
- facebook/react#28226
- facebook/react#28308
- facebook/react#27563
- facebook/react#28297
- facebook/react#28286
- facebook/react#28284
- facebook/react#28275
- facebook/react#28145
- facebook/react#28301
- facebook/react#28224
- facebook/react#28152
- facebook/react#28296
- facebook/react#28294
- facebook/react#28279
- facebook/react#28273
- facebook/react#28269

Closes NEXT-2542


Disable ppr test for strict mode for now, @acdlite will check it and
we'll sync again
eps1lon added a commit to eps1lon/react that referenced this pull request Apr 8, 2024
eps1lon added a commit to eps1lon/react that referenced this pull request Apr 8, 2024
eps1lon added a commit to eps1lon/react that referenced this pull request Apr 8, 2024
EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
…book#28284)

This pains me because `React.Children` is really already
pseudo-deprecated.

`React.Children` takes any children that `React.Node` takes. We now
support Lazy and Thenable in this position elsewhere, but it errors in
`React.Children`.

This becomes an issue with async Server Components which can resolve
into a Lazy and in the future Lazy will just become Thenables. Which
causes this to error.

There are a few different semantics we could have:

1) Error like we already do (facebook#28280). `React.Children` is about
introspecting children. It was always sketchy because you can't
introspect inside an abstraction anyway. With Server Components we fold
away the components so you can actually introspect inside of them kind
of but what they do is an implementation detail and you should be able
to turn it into a Client Component at any point. The type of an Element
passing the boundary actually reduces to `React.Node`.
2) Suspend and unwrap the Node (this PR). If we assume that Children is
called inside of render, then throwing a Promise if it's not already
loaded or unwrapping would treat it as if it wasn't there. Just like if
you rendered it in React. This lets you introspect what's inside which
isn't really something you should be able to do. This isn't compatible
with deprecating throwing-a-Promise and enable static compilation like
`use()` does. We'd have to deprecate `React.Children` before doing that
which we might anyway.
3) Wrap in a Fragment. If a Server Component was instead a Client
Component, you couldn't introspect through it anyway. Another
alternative might be to let it pass through but then it wouldn't be
given a flat key. We could also wrap it in a Fragment that is keyed.
That way you're always seeing an element. The issue with this solution
is that it wouldn't see the key of the Server Component since that gets
forwarded to the child that is yet to resolve. The nice thing about that
strategy is it doesn't depend on throw-a-Promise but it might not be
keyed correctly when things move.
bigfootjon pushed a commit that referenced this pull request Apr 18, 2024
This pains me because `React.Children` is really already
pseudo-deprecated.

`React.Children` takes any children that `React.Node` takes. We now
support Lazy and Thenable in this position elsewhere, but it errors in
`React.Children`.

This becomes an issue with async Server Components which can resolve
into a Lazy and in the future Lazy will just become Thenables. Which
causes this to error.

There are a few different semantics we could have:

1) Error like we already do (#28280). `React.Children` is about
introspecting children. It was always sketchy because you can't
introspect inside an abstraction anyway. With Server Components we fold
away the components so you can actually introspect inside of them kind
of but what they do is an implementation detail and you should be able
to turn it into a Client Component at any point. The type of an Element
passing the boundary actually reduces to `React.Node`.
2) Suspend and unwrap the Node (this PR). If we assume that Children is
called inside of render, then throwing a Promise if it's not already
loaded or unwrapping would treat it as if it wasn't there. Just like if
you rendered it in React. This lets you introspect what's inside which
isn't really something you should be able to do. This isn't compatible
with deprecating throwing-a-Promise and enable static compilation like
`use()` does. We'd have to deprecate `React.Children` before doing that
which we might anyway.
3) Wrap in a Fragment. If a Server Component was instead a Client
Component, you couldn't introspect through it anyway. Another
alternative might be to let it pass through but then it wouldn't be
given a flat key. We could also wrap it in a Fragment that is keyed.
That way you're always seeing an element. The issue with this solution
is that it wouldn't see the key of the Server Component since that gets
forwarded to the child that is yet to resolve. The nice thing about that
strategy is it doesn't depend on throw-a-Promise but it might not be
keyed correctly when things move.

DiffTrain build for commit 9e7944f.
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.

4 participants