Commit Graph

381 Commits

Author SHA1 Message Date
Josh Story
94eed63c49 (Land #28798) Move Current Owner (and Cache) to an Async Dispatcher (#28912)
Rebasing and landing https://github.com/facebook/react/pull/28798

This PR was approved already but held back to give time for the sync.
Rebased and landing here without pushing to seb's remote to avoid
possibility of lost updates

---------

Co-authored-by: Sebastian Markbage <sebastian@calyptus.eu>
2024-04-25 10:40:40 -07:00
Sebastian Markbåge
9f2eebd807 [Fiber/Fizz] Support AsyncIterable as Children and AsyncGenerator Client Components (#28868)
Stacked on #28849, #28854, #28853. Behind a flag.

If you're following along from the side-lines. This is probably not what
you think it is.

It's NOT a way to get updates to a component over time. The
AsyncIterable works like an Iterable already works in React which is how
an Array works. I.e. it's a list of children - not the value of a child
over time.

It also doesn't actually render one component at a time. The way it
works is more like awaiting the entire list to become an array and then
it shows up. Before that it suspends the parent.

To actually get these to display one at a time, you have to opt-in with
`<SuspenseList>` to describe how they should appear. That's really the
interesting part and that not implemented yet.

Additionally, since these are effectively Async Functions and uncached
promises, they're not actually fully "supported" on the client yet for
the same reason rendering plain Promises and Async Functions aren't.
They warn. It's only really useful when paired with RSC that produces
instrumented versions of these. Ideally we'd published instrumented
helpers to help with map/filter style operations that yield new
instrumented AsyncIterables.

The way the implementation works basically just relies on unwrapThenable
and otherwise works like a plain Iterator.

There is one quirk with these that are different than just promises. We
ask for a new iterator each time we rerender. This means that upon retry
we kick off another iteration which itself might kick off new requests
that block iterating further. To solve this and make it actually
efficient enough to use on the client we'd need to stash something like
a buffer of the previous iteration and maybe iterator on the iterable so
that we can continue where we left off or synchronously iterate if we've
seen it before. Similar to our `.value` convention on Promises.

In Fizz, I had to do a special case because when we render an iterator
child we don't actually rerender the parent again like we do in Fiber.
However, it's more efficient to just continue on where we left off by
reusing the entries from the thenable state from before in that case.
2024-04-22 13:25:05 -04:00
Sebastian Markbåge
5b903cdaa9 [Flight] Support (Async) Generator ServerComponent (#28849)
Stacked on #28853 and #28854.

React supports rendering `Iterable` and will soon support
`AsyncIterable`. As long as it's multi-shot since during an update we
may have to rerender with new inputs an loop over the iterable again.
Therefore the `Iterator` and `AsyncIterator` types are not supported
directly as a child of React - and really it shouldn't pass between
Hooks or components neither for this reason. For parity, that's also the
case when used in Server Components.

However, there is a special case when the component rendered itself is a
generator function. While it returns as a child an `Iterator`, the React
Element itself can act as an `Iterable` because we can re-evaluate the
function to create a new generator whenever we need to.

It's also very convenient to use generator functions over constructing
an `AsyncIterable`. So this is a proposal to special case the
`Generator`/`AsyncGenerator` returned by a (Async) Generator Function.

In Flight this means that when we render a Server Component we can
serialize this value as an `Iterable`/`AsyncIterable` since that's
effectively what rendering it on the server reduces down to. That way if
Fiber can receive the result in any position.

For SuspenseList this would also need another special case because the
children of SuspenseList represent "rows".

`<SuspenseList><Component /></SuspenseList>` currently is a single "row"
even if the component renders multiple children or is an iterator. This
is currently different if Component is a Server Component because it'll
reduce down to an array/AsyncIterable and therefore be treated as one
row per its child. This is different from `<SuspenseList><Component
/><Component /></SuspenseList>` since that has a wrapper array and so
this is always two rows.

It probably makes sense to special case a single-element child in
`SuspenseList` to represent a component that generates rows. That way
you can use an `AsyncGeneratorFunction` to do this.
2024-04-21 13:10:10 -04:00
Sebastian Markbåge
bf426f9b1d [Flight / Flight Reply] Encode Iterator separately from Iterable (#28854)
For [`AsyncIterable`](https://github.com/facebook/react/pull/28847) we
encode `AsyncIterator` as a separate tag.

Previously we encoded `Iterator` as just an Array. This adds a special
encoding for this. Technically this is a breaking change.

This is kind of an edge case that you'd care about the difference but it
becomes more important to treat these correctly for the warnings here
#28853.
2024-04-21 12:52:04 -04:00
Sebastian Markbåge
368202181e Warn for Child Iterator of all types but allow Generator Components (#28853)
This doesn't change production behavior. We always render Iterables to
our best effort in prod even if they're Iterators.

But this does change the DEV warnings which indicates which are valid
patterns to use.

It's a footgun to use an Iterator as a prop when you pass between
components because if an intermediate component rerenders without its
parent, React won't be able to iterate it again to reconcile and any
mappers won't be able to re-apply. This is actually typically not a
problem when passed only to React host components but as a pattern it's
a problem for composability.

We used to warn only for Generators - i.e. Iterators returned from
Generator functions. This adds a warning for Iterators created by other
means too (e.g. Flight or the native Iterator utils). The heuristic is
to check whether the Iterator is the same as the Iterable because that
means it's not possible to get new iterators out of it. This case used
to just yield non-sense like empty sets in DEV but not in prod.

However, a new realization is that when the Component itself is a
Generator Function, it's not actually a problem. That's because the
React Element itself works as an Iterable since we can ask for new
generators by calling the function again. So this adds a special case to
allow the Generator returned from a Generator Function's direct child.
The principle is “don’t pass iterators around” but in this case there is
no iterator floating around because it’s between React and the JS VM.

Also see #28849 for context on AsyncIterables.

Related to this, but Hooks should ideally be banned in these for the
same reason they're banned in Async Functions.
2024-04-21 12:51:45 -04:00
Andrew Clark
857ee8cdf9 Don't minify symbols in production builds (#28881)
This disables symbol renaming in production builds. The original
variable and function names are preserved. All other forms of
compression applied by Closure (dead code elimination, inlining, etc)
are unchanged — the final program is identical to what we were producing
before, just in a more readable form.

The motivation is to make it easier to debug React issues that only
occur in production — the same reason we decided to start shipping
sourcemaps in #28827 and #28827.

However, because most apps run their own minification step on their npm
dependencies, it's not necessary for us to minify the symbols before
publishing — it'll be handled the app, if desired.

This is the same strategy Meta has used to ship React for years. The
React build itself has unminified symbols, but they get minified as part
of Meta's regular build pipeline.

Even if an app does not minify their npm dependencies, gzip covers most
of the cost of symbol renaming anyway.

This saves us from having to ship sourcemaps, which means even apps that
don't have sourcemaps configured will be able to debug the React build
as easily as they would any other npm dependency.
2024-04-20 11:23:46 -04:00
Jan Kassens
1cd77a4ff7 Remove ReactFlightFB bundles (#28864)
Remove ReactFlightFB bundles
2024-04-18 16:41:04 -04:00
Sebastian Markbåge
c0cf7c696c Promote ASYNC_ITERATOR symbol to React Symbols (#28851)
So that when we end up referring to it in more places, it's only one.

We don't do this same pattern for regular `Symbol.iterator` because we
also support the string `"@@iterator"` for backwards compatibility.
2024-04-17 12:29:08 -04:00
Sebastian Markbåge
7909d8eabb [Flight] Encode ReadableStream and AsyncIterables (#28847)
This adds support in Flight for serializing four kinds of streams:

- `ReadableStream` with objects as a model. This is a single shot
iterator so you can read it only once. It can contain any value
including Server Components. Chunks are encoded as is so if you send in
10 typed arrays, you get the same typed arrays out on the other side.
- Binary `ReadableStream` with `type: 'bytes'` option. This supports the
BYOB protocol. In this mode, the receiving side just gets `Uint8Array`s
and they can be split across any single byte boundary into arbitrary
chunks.
- `AsyncIterable` where the `AsyncIterator` function is different than
the `AsyncIterable` itself. In this case we assume that this might be a
multi-shot iterable and so we buffer its value and you can iterate it
multiple times on the other side. We support the `return` value as a
value in the single completion slot, but you can't pass values in
`next()`. If you want single-shot, return the AsyncIterator instead.
- `AsyncIterator`. These gets serialized as a single-shot as it's just
an iterator.

`AsyncIterable`/`AsyncIterator` yield Promises that are instrumented
with our `.status`/`.value` convention so that they can be synchronously
looped over if available. They are also lazily parsed upon read.

We can't do this with `ReadableStream` because we use the native
implementation of `ReadableStream` which owns the promises.

The format is a leading row that indicates which type of stream it is.
Then a new row with the same ID is emitted for every chunk. Followed by
either an error or close row.

`AsyncIterable`s can also be returned as children of Server Components
and then they're conceptually the same as fragment arrays/iterables.
They can't actually be used as children in Fizz/Fiber but there's a
separate plan for that. Only `AsyncIterable` not `AsyncIterator` will be
valid as children - just like sync `Iterable` is already supported but
single-shot `Iterator` is not. Notably, neither of these streams
represent updates over time to a value. They represent multiple values
in a list.

When the server stream is aborted we also close the underlying stream.
However, closing a stream on the client, doesn't close the underlying
stream.

A couple of possible follow ups I'm not planning on doing right now:

- [ ] Free memory by releasing the buffer if an Iterator has been
exhausted. Single shots could be optimized further to release individual
items as you go.
- [ ] We could clean up the underlying stream if the only pending data
that's still flowing is from streams and all the streams have cleaned
up. It's not very reliable though. It's better to do cancellation for
the whole stream - e.g. at the framework level.
- [ ] Implement smarter Binary Stream chunk handling. Currently we wait
until we've received a whole row for binary chunks and copy them into
consecutive memory. We need this to preserve semantics when passing
typed arrays. However, for binary streams we don't need that. We can
just send whatever pieces we have so far.
2024-04-16 12:20:07 -04:00
Sebastian Markbåge
17e920c00d [Flight Reply] Encode Typed Arrays and Blobs (#28819)
With the enableBinaryFlight flag on we should encode typed arrays and
blobs in the Reply direction too for parity.

It's already possible to pass Blobs inside FormData but you should be
able to pass them inside objects too.

We encode typed arrays as blobs and then unwrap them automatically to
the right typed array type.

Unlike the other protocol, I encode the type as a reference tag instead
of row tag. Therefore I need to rename the tags to avoid conflicts with
other tags in references. We are running out of characters though.
2024-04-15 22:32:08 -04:00
Kenta Iwasaki
c113503ad1 Flush direct streams in Bun (#28837)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

The ReadableStreamController for [direct
streams](https://bun.sh/docs/api/streams#direct-readablestream) in Bun
supports a flush() method to flush all buffered items to its underlying
sink.

Without manually calling flush(), all buffered items are only flushed to
the underlying sink when the stream is closed. This behavior causes the
shell rendered against Suspense boundaries never to be flushed to the
underlying sink.

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->

A lot of changes to the test runner will need to be made in order to
support the Bun runtime. A separate test was manually run in order to
ensure that the changes made are correct.

The test works by sanity-checking that the shell rendered against
Suspense boundaries are emitted first in the stream.

This test was written and run on Bun v1.1.3.

```ts
import { Suspense } from "react";
import { renderToReadableStream } from "react-dom/server";

if (!import.meta.resolveSync("react-dom/server").endsWith("server.bun.js")) {
  throw new Error("react-dom/server is not the correct version:\n  " + import.meta.resolveSync("react-dom/server"));
}

const A = async () => {
  await new Promise(resolve => setImmediate(resolve));
  return <div>hi</div>;
};

const B = async () => {
  return (
    <Suspense fallback={<div>loading</div>}>
      <A />
    </Suspense>
  );
};

const stream = await renderToReadableStream(<B />);

let text = "";
let count = 0;
for await (const chunk of stream) {
  text += new TextDecoder().decode(chunk);
  count++;
}

if (
  text !==
  `<!--$?--><template id="B:0"></template><div>loading</div><!--/$--><div hidden id="S:0"><div>hi</div></div><script>$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};$RC("B:0","S:0")</script>`
) {
  throw new Error("unexpected output");
}
if (count !== 2) {
  throw new Error("expected 2 chunks from react ssr stream");
}
```
2024-04-15 11:25:08 -04:00
Josh Story
c8a035036d [Fizz] hoistables should never flush before the preamble (#28802)
Hoistables should never flush before the preamble however there is a
surprisingly easy way to trigger this to happen by suspending in the
shell of the app. This change modifies the flushing behavior to not emit
any hoistables before the preamble has written. It accomplishes this by
aborting the flush early if there are any pending root tasks remaining.
It's unfortunate we need this extra condition but it's essential that we
don't emit anything before the preamble and at the moment I don't see a
way to do that without introducing a new condition.

There is a test that began to fail with this update. It turns out that
in node the root can be blocked during a resume even for a component
inside a Suspense boundary if that boundary was part of the prerender.
This means that with the current heuristic in this PR boundaries cannot
be flushed during resume until the root is unblocked. This is not ideal
but this is already how Edge works because the root blocks the stream in
that case. This just makes Node deopt in a similar way to edge. We
should improve this but we ought to do so in a way that works for edge
too and it needs to be more comprehensive.
2024-04-11 15:13:04 -07:00
Sebastian Markbåge
f613165357 Rename SECRET INTERNALS to __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE (#28789)
Follow up to #28783 and #28786.

Since we've changed the implementations of these we can rename them to
something a bit more descriptive while we're at it, since anyone
depending on them will need to upgrade their code anyway.

"react" with no condition:
`__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE`
"react" with "react-server" condition:
`__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE`
"react-dom":
`__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE`
2024-04-09 12:20:22 -04:00
Sebastian Markbåge
c771016e19 Rename The Secret Export of Server Internals (#28786)
We have a different set of dispatchers that Flight uses. This also
includes the `jsx-runtime` which must also be aliased to use the right
version.

To ensure the right versions are used together we rename the export of
the SharedInternals from 'react' and alias it in relevant bundles.
2024-04-08 22:34:59 -04:00
Sebastian Markbåge
d50323eb84 Flatten ReactSharedInternals (#28783)
This is similar to #28771 but for isomorphic. We need a make over for
these dispatchers anyway so this is the first step. Also helps flush out
some internals usage that will break anyway.

It flattens the inner mutable objects onto the ReactSharedInternals.
2024-04-08 19:23:23 -04:00
Sebastian Markbåge
14f50ad155 [Flight] Allow lazily resolving outlined models (#28780)
We used to assume that outlined models are emitted before the reference
(which was true before Blobs). However, it still wasn't safe to assume
that all the data will be available because an "import" (client
reference) can be async and therefore if it's directly a child of an
outlined model, it won't be able to update in place.

This is a similar problem as the one hit by @unstubbable in #28669 with
elements, but a little different since these don't follow the same way
of wrapping.

I don't love the structuring of this code which now needs to pass a
first class mapper instead of just being known code. It also shares the
host path which is just an identity function. It wouldn't necessarily
pass my own review but I don't have a better one for now. I'd really
prefer if this was done at a "row" level but that ends up creating even
more code.

Add test for Blob in FormData and async modules in Maps.
2024-04-08 15:40:11 -04:00
Sebastian Markbåge
2acfb7b609 [Flight] Support FormData from Server to Client (#28754)
We currently support FormData for Replies mainly for Form Actions. This
supports it in the other direction too which lets you return it from an
action as the response. Mainly for parity.

We don't really recommend that you just pass the original form data back
because the action is supposed to be able to clear fields and such but
you could potentially at least use this as the format and could clear
some fields.

We could potentially optimize this with a temporary reference if the
same object was passed to a reply in case you use it as a round trip to
avoid serializing it back again. That way the action has the ability to
override it to clear fields but if it doesn't you get back the same as
you sent.

#28755 adds support for Blobs when the `enableBinaryFlight` is enabled
which allows them to be used inside FormData too.
2024-04-05 14:32:18 -04:00
Andrew Clark
bfd8da807c Make class prop resolution faster (#28766)
`delete` causes an object (in V8, and maybe other engines) to deopt to a
dictionary instead of a class. Instead of `assign` + `delete`, manually
iterate over all the properties, like the JSX runtime does.

To avoid copying the object twice I moved the `ref` prop removal to come
before handling default props. If we already cloned the props to remove
`ref`, then we can skip cloning again to handle default props.
2024-04-05 13:06:39 -04:00
Sebastian Markbåge
cbb6f2b546 [Flight] Support Blobs from Server to Client (#28755)
We currently support Blobs when passing from Client to Server so this
adds it in the other direction for parity - when `enableFlightBinary` is
enabled.

We intentionally only support the `Blob` type to pass-through, not
subtype `File`. That's because passing additional meta data like
filename might be an accidental leak. You can still pass a `File`
through but it'll appear as a `Blob` on the other side. It's also not
possible to create a faithful File subclass in all environments without
it actually being backed by a file.

This implementation isn't great but at least it works. It creates a few
indirections. This is because we need to be able to asynchronously emit
the buffers but we have to "block" the parent object from resolving
while it's loading.

Ideally, we should be able to create the Blob on the client early and
then stream in it lazily. Because the Blob API doesn't guarantee that
the data is available synchronously. Unfortunately, the native APIs
doesn't have this. We could implement custom versions of all the data
read APIs but then the blobs still wouldn't work with native APIs. So we
just have to wait until Blob accepts a stream in the constructor.

We should be able to stream each chunk early in the protocol though even
though we can't unblock the parent until they've all loaded. I didn't do
this yet mostly because of code structure and I'm lazy.
2024-04-05 12:49:25 -04:00
Sebastian Markbåge
f33a6b69c6 Track Owner for Server Components in DEV (#28753)
This implements the concept of a DEV-only "owner" for Server Components.
The owner concept isn't really super useful. We barely use it anymore,
but we do have it as a concept in DevTools in a couple of cases so this
adds it for parity. However, this is mainly interesting because it could
be used to wire up future owner-based stacks.

I do this by outlining the DebugInfo for a Server Component
(ReactComponentInfo). Then I just rely on Flight deduping to refer to
that. I refer to the same thing by referential equality so that we can
associate a Server Component parent in DebugInfo with an owner.

If you suspend and replay a Server Component, we have to restore the
same owner. To do that, I did a little ugly hack and stashed it on the
thenable state object. Felt unnecessarily complicated to add a stateful
wrapper for this one dev-only case.

The owner could really be anything since it could be coming from a
different implementation. Because this is the first time we have an
owner other than Fiber, I have to fix up a bunch of places that assumes
Fiber. I mainly did the `typeof owner.tag === 'number'` to assume it's a
Fiber for now.

This also doesn't actually add it to DevTools / RN Inspector yet. I just
ignore them there for now.

Because Server Components can be async the owner isn't tracked after an
await. We need per-component AsyncLocalStorage for that. This can be
done in a follow up.
2024-04-05 12:48:52 -04:00
Andrew Clark
48b4ecc901 Remove defaultProps support (except for classes) (#28733)
This removes defaultProps support for all component types except for
classes. We've chosen to continue supporting defaultProps for classes
because lots of older code relies on it, and unlike function components,
(which can use default params), there's no straightforward alternative.

By implication, it also removes support for setting defaultProps on
`React.lazy` wrapper. So this will not work:

```js
const MyClassComponent = React.lazy(() => import('./MyClassComponent'));
// MyClassComponent is not actually a class; it's a lazy wrapper. So
// defaultProps does not work.
MyClassComponent.defaultProps = { foo: 'bar' };
```

However, if you set the default props on the class itself, then it's
fine.

For classes, this change also moves where defaultProps are resolved.
Previously, defaultProps were resolved by the JSX runtime. This change
is only observable if you introspect a JSX element, which is relatively
rare but does happen.

In other words, previously `<ClassWithDefaultProp />.props.aDefaultProp`
would resolve to the default prop value, but now it does not.
2024-04-04 10:59:19 -04:00
Sebastian Markbåge
583eb6770d Emit Server Error Prefix in the .stack Property Too (#28738)
Follow up to #28684.

V8 includes the message in the stack and printed errors include just the
stack property which is assumed to contain the message. Without this,
the prefix doesn't get printed in the console.

<img width="578" alt="Screenshot 2024-04-03 at 6 32 04 PM"
src="https://github.com/facebook/react/assets/63648/d98a2db4-6ebc-4805-b669-59f449dfd21f">

A possible alternative would be to use a nested error with a `cause`
like #28736 but that would need some more involved serializing since
this prefix is coming from the server. Perhaps as a separate attribute.
2024-04-03 21:52:54 -04:00
Andrew Clark
3761acb42b Classes consume ref prop during SSR, too (#28731)
Same as #28719 but for SSR.
2024-04-03 12:55:57 -04:00
Jan Kassens
7a2609eedc Cleanup enableBigIntSupport flag (#28711)
Cleanup enableBigIntSupport flag
2024-04-03 09:25:02 -04:00
Josh Story
5998a77519 Reland #28672: Remove IndeterminateComponent (#28681)
This PR relands #28672 on top of the flag removal and the test
demonstrating a breakage in Suspense for legacy mode.

React has deprecated module pattern Function Components for many years
at this point. Supporting this pattern required React to have a concept
of an indeterminate component so that when a component first renders it
can turn into either a ClassComponent or a FunctionComponent depending
on what it returns. While this feature was deprecated and put behind a
flag it is still in stable. This change remvoes the flag, removes the
warnings, and removes the concept of IndeterminateComponent from the
React codebase.

While removing IndeterminateComponent type Seb and I discovered that we
needed a concept of IncompleteFunctionComponent to support Suspense in
legacy mode. This new work tag is only needed as long as legacy mode is
around and ideally any code that considers this tag will be excludable
from OSS builds once we land extra gates using `disableLegacyMode` flag.
2024-04-02 17:42:43 -07:00
Joseph Savona
8cb6a1c034 [be] Remove unused, experimental getCacheSignal API (#28706)
Similar to #28698, this removes the `unstable_getCacheSignal()` API
since we don't intend to ship this to stable.
2024-04-02 10:54:31 -07:00
Hendrik Liebau
93f91795a0 [Flight] Update stale blocked values in createModelResolver (#28669)
Alternative to #28620.

Instead of emitting lazy references to not-yet-emitted models in the
Flight Server, this fixes the observed issue in
https://github.com/unstubbable/ai-rsc-test/pull/1 by adjusting the lazy
model resolution in the Flight Client to update stale blocked root
models, before assigning them as chunk values. In addition, the element
props are not outlined anymore in the Flight Server to avoid having to
also handle their staleness in blocked elements.

fixes #28595
2024-04-01 12:37:27 -04:00
Sebastian Markbåge
b9149cc6e6 Include regular stack trace in serialized errors from Fizz (#28684)
We previously only included the component stack.
    
Cleaned up the fields in Fizz server that wasn't using consistent hidden
classes in dev vs prod.

Added a prefix to errors serialized from server rendering. It can be a
bit confusing to see where this error came from otherwise since it
didn't come from elsewhere on the client. It's really kind of confusing
with other recoverable errors that happen on the client too.
2024-03-30 11:08:54 -04:00
Jan Kassens
a73c3450e1 Remove module pattern function component support (flag only) (#28671)
Remove module pattern function component support (flag only)

> This is a redo of #27742, but only including the flag removal,
excluding further simplifications.

The module pattern

```
function MyComponent() {
  return {
    render() {
      return this.state.foo
    }
  }
}
```

has been deprecated for approximately 5 years now. This PR removes
support for this pattern.
2024-03-29 11:16:17 -04:00
Ricky
f269074723 Revert "Remove module pattern function component support" (#28670)
This breaks internal tests, so must be something in the refactor. Since
it's the top commit let's revert and split into two PRs, one that
removes the flag and one that does the refactor, so we can find the bug.
2024-03-29 10:10:11 -04:00
Josh Story
cc56bed38c Remove module pattern function component support (#27742)
The module pattern

```
function MyComponent() {
  return {
    render() {
      return this.state.foo
    }
  }
}
```

has been deprecated for approximately 5 years now. This PR removes
support for this pattern. It also simplifies a number of code paths in
particular related to the concept of `IndeterminateComponent` types.
2024-03-28 13:08:08 -07:00
Ricky
05797ccebd s/form state/action state (#28631)
Rename internals from "form state" to "action state"
2024-03-28 11:34:24 -04:00
Ricky
dbfbfb3312 Update error messages (#28652)
## Overview

The error messages that say:

> ReactDOM.hydrate is no longer supported in React 18

Don't make sense in the React 19 release. Instead, they should say:

> ReactDOM.hydrate was removed in React 19.

For legacy mode, they should say:

> ReactDOM.hydrate has not been supported since React 18.
2024-03-26 17:25:53 -04:00
Jan Kassens
527ed72bfd Cleanup enableFormActions flag (#28614)
Cleanup enableFormActions flag
2024-03-25 13:25:14 -04:00
Ricky
5c65b27587 Add React.useActionState (#28491)
## Overview

_Depends on https://github.com/facebook/react/pull/28514_

This PR adds a new React hook called `useActionState` to replace and
improve the ReactDOM `useFormState` hook.

## Motivation

This hook intends to fix some of the confusion and limitations of the
`useFormState` hook.

The `useFormState` hook is only exported from the `ReactDOM` package and
implies that it is used only for the state of `<form>` actions, similar
to `useFormStatus` (which is only for `<form>` element status). This
leads to understandable confusion about why `useFormState` does not
provide a `pending` state value like `useFormStatus` does.

The key insight is that the `useFormState` hook does not actually return
the state of any particular form at all. Instead, it returns the state
of the _action_ passed to the hook, wrapping it and returning a
trackable action to add to a form, and returning the last returned value
of the action given. In fact, `useFormState` doesn't need to be used in
a `<form>` at all.

Thus, adding a `pending` value to `useFormState` as-is would thus be
confusing because it would only return the pending state of the _action_
given, not the `<form>` the action is passed to. Even if we wanted to
tie them together, the returned `action` can be passed to multiple
forms, creating confusing and conflicting pending states during multiple
form submissions.

Additionally, since the action is not related to any particular
`<form>`, the hook can be used in any renderer - not only `react-dom`.
For example, React Native could use the hook to wrap an action, pass it
to a component that will unwrap it, and return the form result state and
pending state. It's renderer agnostic.

To fix these issues, this PR:
- Renames `useFormState` to `useActionState`
- Adds a `pending` state to the returned tuple
- Moves the hook to the `'react'` package

## Reference

The `useFormState` hook allows you to track the pending state and return
value of a function (called an "action"). The function passed can be a
plain JavaScript client function, or a bound server action to a
reference on the server. It accepts an optional `initialState` value
used for the initial render, and an optional `permalink` argument for
renderer specific pre-hydration handling (such as a URL to support
progressive hydration in `react-dom`).

Type:

```ts
function useActionState<State>(
        action: (state: Awaited<State>) => State | Promise<State>,
        initialState: Awaited<State>,
        permalink?: string,
    ): [state: Awaited<State>, dispatch: () => void, boolean];
```

The hook returns a tuple with:
- `state`: the last state the action returned
- `dispatch`: the method to call to dispatch the wrapped action
- `pending`: the pending state of the action and any state updates
contained

Notably, state updates inside of the action dispatched are wrapped in a
transition to keep the page responsive while the action is completing
and the UI is updated based on the result.

## Usage

The `useActionState` hook can be used similar to `useFormState`:

```js
import { useActionState } from "react"; // not react-dom

function Form({ formAction }) {
  const [state, action, isPending] = useActionState(formAction);

  return (
    <form action={action}>
      <input type="email" name="email" disabled={isPending} />
      <button type="submit" disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </form>
  );
}
```

But it doesn't need to be used with a `<form/>` (neither did
`useFormState`, hence the confusion):

```js
import { useActionState, useRef } from "react";

function Form({ someAction }) {
  const ref = useRef(null);
  const [state, action, isPending] = useActionState(someAction);

  async function handleSubmit() {
    // See caveats below
    await action({ email: ref.current.value });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}
```

## Benefits

One of the benefits of using this hook is the automatic tracking of the
return value and pending states of the wrapped function. For example,
the above example could be accomplished via:

```js
import { useActionState, useRef } from "react";

function Form({ someAction }) {
  const ref = useRef(null);
  const [state, setState] = useState(null);
  const [isPending, setIsPending] = useTransition();

  function handleSubmit() {
    startTransition(async () => {
      const response = await someAction({ email: ref.current.value });
      setState(response);
    });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}
```

However, this hook adds more benefits when used with render specific
elements like react-dom `<form>` elements and Server Action. With
`<form>` elements, React will automatically support replay actions on
the form if it is submitted before hydration has completed, providing a
form of partial progressive enhancement: enhancement for when javascript
is enabled but not ready.

Additionally, with the `permalink` argument and Server Actions,
frameworks can provide full progressive enhancement support, submitting
the form to the URL provided along with the FormData from the form. On
submission, the Server Action will be called during the MPA navigation,
similar to any raw HTML app, server rendered, and the result returned to
the client without any JavaScript on the client.

## Caveats
There are a few Caveats to this new hook:
**Additional state update**: Since we cannot know whether you use the
pending state value returned by the hook, the hook will always set the
`isPending` state at the beginning of the first chained action,
resulting in an additional state update similar to `useTransition`. In
the future a type-aware compiler could optimize this for when the
pending state is not accessed.

**Pending state is for the action, not the handler**: The difference is
subtle but important, the pending state begins when the return action is
dispatched and will revert back after all actions and transitions have
settled. The mechanism for this under the hook is the same as
useOptimisitic.

Concretely, what this means is that the pending state of
`useActionState` will not represent any actions or sync work performed
before dispatching the action returned by `useActionState`. Hopefully
this is obvious based on the name and shape of the API, but there may be
some temporary confusion.

As an example, let's take the above example and await another action
inside of it:

```js
import { useActionState, useRef } from "react";

function Form({ someAction, someOtherAction }) {
  const ref = useRef(null);
  const [state, action, isPending] = useActionState(someAction);

  async function handleSubmit() {
    await someOtherAction();

    // The pending state does not start until this call.
    await action({ email: ref.current.value });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}

```

Since the pending state is related to the action, and not the handler or
form it's attached to, the pending state only changes when the action is
dispatched. To solve, there are two options.

First (recommended): place the other function call inside of the action
passed to `useActionState`:

```js
import { useActionState, useRef } from "react";

function Form({ someAction, someOtherAction }) {
  const ref = useRef(null);
  const [state, action, isPending] = useActionState(async (data) => {
    // Pending state is true already.
    await someOtherAction();
    return someAction(data);
  });

  async function handleSubmit() {
    // The pending state starts at this call.
    await action({ email: ref.current.value });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}
```

For greater control, you can also wrap both in a transition and use the
`isPending` state of the transition:

```js
import { useActionState, useTransition, useRef } from "react";

function Form({ someAction, someOtherAction }) {
  const ref = useRef(null);

  // isPending is used from the transition wrapping both action calls.
  const [isPending, startTransition] = useTransition();

  // isPending not used from the individual action.
  const [state, action] = useActionState(someAction);

  async function handleSubmit() {
    startTransition(async () => {
      // The transition pending state has begun.
      await someOtherAction();
      await action({ email: ref.current.value });
    });
  }

  return (
    <div>
      <input ref={ref} type="email" name="email" disabled={isPending} />
      <button onClick={handleSubmit} disabled={isPending}>
        Submit
      </button>
      {state.errorMessage && <p>{state.errorMessage}</p>}
    </div>
  );
}
```

A similar technique using `useOptimistic` is preferred over using
`useTransition` directly, and is left as an exercise to the reader.

## Thanks

Thanks to @ryanflorence @mjackson @wesbos
(https://github.com/facebook/react/issues/27980#issuecomment-1960685940)
and [Allan
Lasser](https://allanlasser.com/posts/2024-01-26-avoid-using-reacts-useformstatus)
for their feedback and suggestions on `useFormStatus` hook.
2024-03-22 13:03:44 -04:00
Jan Kassens
208ceeb46c Cleanup enableFloat flag (#28613)
Cleanup enableFloat flag
2024-03-22 12:22:30 -04:00
Jan Kassens
6708115937 Use declare const instead of declare var (#28599)
Use `declare const` instead of `declare var`
2024-03-22 11:20:18 -04:00
Sebastian Markbåge
72e02a8350 [Flight Reply] Don't allow Symbols to be passed to a reply (#28610)
As mentioned in #28609 there's a potential security risk if you allow a
passed value to the server to spoof Elements because it allows a hacker
to POST cross origin. This is only an issue if your framework allows
this which it shouldn't but it seems like we should provide an extra
layer of security here.

```js
function action(errors, payload) {
  try {
    ...
  } catch (x) {
    return [newError].concat(errors);
  }
}
```
```js
const [errors, formAction] = useActionState(action);
return <div>{errors}</div>;
```
This would allow you to construct a payload where the previous "errors"
set includes something like `<script src="danger.js" />`.

We could block only elements from being received but it could
potentially be a risk with creating other React types like Context too.
We use symbols as a way to securely brand these.

Most JS don't use this kind of branding with symbols like we do. They're
generally properties which we don't support anyway. However in theory
someone else could be using them like we do. So in an abundance of
carefulness I just ban all symbols from being passed (except by
temporary reference) - not just ours.

This means that the format isn't fully symmetric even beyond just React
Nodes.

#28611 allows code that includes symbols/elements to continue working
but may have to bail out to replaying instead of no JS sometimes.
However, you still can't access the symbols inside the server - they're
by reference only.
2024-03-21 16:53:02 -04:00
Sebastian Markbåge
83409a1fdd [Flight] Encode React Elements in Replies as Temporary References (#28564)
Currently you can accidentally pass React Element to a Server Action. It
warns but in prod it actually works because we can encode the symbol and
otherwise it's mostly a plain object. It only works if you only pass
host components and no function props etc. which makes it potentially
error later. The first thing this does it just early hard error for
elements.

I made Lazy work by unwrapping though since that will be replaced by
Promises later which works.

Our protocol is not fully symmetric in that elements flow from Server ->
Client. Only the Server can resolve Components and only the client
should really be able to receive host components. It's not intended that
a Server can actually do something with them other than passing them to
the client.

In the case of a Reply, we expect the client to be stateful. It's
waiting for a response. So anything we can't serialize we can still pass
by reference to an in memory object. So I introduce the concept of a
TemporaryReferenceSet which is an opaque object that you create before
encoding the reply. This then stashes any unserializable values in this
set and encode the slot by id. When a new response from the Action then
returns we pass the same temporary set into the parser which can then
restore the objects. This lets you pass a value by reference to the
server and back into another slot.

For example it can be used to render children inside a parent tree from
a server action:

```
export async function Component({ children }) {
  "use server";
  return <div>{children}</div>;
}
```

(You wouldn't normally do this due to the waterfalls but for advanced
cases.)

A common scenario where this comes up accidentally today is in
`useActionState`.

```
export function action(state, formData) {
  "use server";
   if (errored) {
     return <div>This action <strong>errored</strong></div>;
   }
   return null;
}
```

```
const [errors, formAction] = useActionState(action);
return <div>{errors}<div>;
```

It feels like I'm just passing the JSX from server to client. However,
because `useActionState` also sends the previous state *back* to the
server this should not actually be valid. Before this PR this actually
worked accidentally. You get a DEV warning but it used to work in prod.
Once you do something like pass a client reference it won't work tho. We
could perhaps make client references work by stashing where we got them
from but it wouldn't work with all possible JSX.

By adding temporary references to the action implementation this will
work again - on the client. It'll also be more efficient since we don't
send back the JSX content that you shouldn't introspect on the server
anyway.

However, a flaw here is that the progressive enhancement of this case
won't work because we can't use temporary references for progressive
enhancement since there's no in memory stash. What is worse is that it
won't error if you hydrate. ~It also will error late in the example
above because the first state is "undefined" so invoking the form once
works - it errors on the second attempt when it tries to send the error
state back again.~ It actually errors on the first invocation because we
need to eagerly serialize "previous state" into the form. So at least
that's better.

I think maybe the solution to this particular pattern would be to allow
JSX to serialize if you have no temporary reference set, and remember
client references so that client references can be returned back to the
server as client references. That way anything you could send from the
server could also be returned to the server. But it would only deopt to
serializing it for progressive enhancement. The consequence of that
would be that there's a lot of JSX that might accidentally seem like it
should work but it's only if you've gotten it from the server before
that it works. This would have to have pair them somehow though since
you can't take a client reference from one implementation of Flight and
use it with another.
2024-03-19 16:59:52 -04:00
Josh Story
b09e102ff1 [Fizz] Prevent uncloned large precomputed chunks without relying on render-time assertions (#28568)
A while back we implemented a heuristic that if a chunk was large it was
assumed to be produced by the render and thus was safe to stream which
results in transferring the underlying object memory. Later we ran into
an issue where a precomputed chunk grew large enough to trigger this
hueristic and it started causing renders to fail because once a second
render had occurred the precomputed chunk would not have an underlying
buffer of bytes to send and these bytes would be omitted from the
stream. We implemented a technique to detect large precomputed chunks
and we enforced that these always be cloned before writing.
Unfortunately our test coverage was not perfect and there has been for a
very long time now a usage pattern where if you complete a boundary in
one flush and then complete a boundary that has stylehsheet dependencies
in another flush you can get a large precomputed chunk that was not
being cloned to be sent twice causing streaming errors.

I've thought about why we even went with this solution in the first
place and I think it was a mistake. It relies on a dev only check to
catch paired with potentially version specific order of operations on
the streaming side. This is too unreliable. Additionally the low limit
of view size for Edge is not used in Node.js but there is not real
justification for this.

In this change I updated the view size for edge streaming to match Node
at 2048 bytes which is still relatively small and we have no data one
way or another to preference 512 over this. Then I updated the assertion
logic to error anytime a precomputed chunk exceeds the size. This
eliminates the need to clone these chunks by just making sure our view
size is always larger than the largest precomputed chunk we can possibly
write. I'm generally in favor of this for a few reasons.

First, we'll always know during testing whether we've violated the limit
as long as we exercise each stream config because the precomputed chunks
are created in module scope. Second, we can always split up large chunks
so making sure the precomptued chunk is smaller than whatever view size
we actually desire is relatively trivial.
2024-03-16 12:39:37 -07:00
Ricky
17eaacaac1 Add pending state to useFormState (#28514)
## Overview

Adds a `pending` state to useFormState, which will be replaced by
`useActionState` in the next diff. We will keep `useFormState` around
for backwards compatibility, but functionally it will work the same as
`useActionState`, which has an `isPending` state returned.
2024-03-12 15:50:11 -04:00
Josh Story
113ab9af08 [Flight][Fizz][Fiber] Chain HostDispatcher implementations (#28488)
The idea here is that host dispatchers are not bound to renders so we
need to be able to dispatch to them at any time. This updates the
implementation to chain these dispatchers so that each renderer can
respond to the dispatch. Semantically we don't always want every
renderer to do this for instance if Fizz handles a float method we don't
want Fiber to as well so each dispatcher implementation can decide if it
makes sense to forward the call or not. For float methods server
disaptchers will handle the call if they can resolve a Request otherwise
they will forward. For client dispatchers they will handle the call and
always forward. The choice needs to be made for each dispatcher method
and may have implications on correct renderer import order. For now we
just live with the restriction that if you want to use server and client
together (such as renderToString in the browser) you need to import the
server renderer after the client renderer.
2024-03-04 12:27:15 -08:00
Ricky
1940cb27b2 Update /link URLs to react.dev (#28477)
Depends on https://github.com/reactjs/react.dev/pull/6670 [merged]
2024-03-03 17:34:33 -05:00
dan
11828bf529 Remove loose-envify dep and browserify configs (#28480)
Browserify isn't widely used, and at this point I don't think it makes
sense to have a special config just for the sake of it. Let's remove it?
2024-03-01 20:49:51 +00:00
Ricky
5f2c6b74db Update homepage URLs to react.dev (#28478)
Updates the package.json "homepage" entry to react.dev
2024-03-01 14:35:18 -05:00
Sebastian Silbermann
2f240c91ed Add support for rendering BigInt (#24580) 2024-02-26 19:18:50 +01:00
Sebastian Markbåge
d579e77482 Remove method name prefix from warnings and errors (#28432)
This pattern is a petpeeve of mine. I don't consider this best practice
and so most don't have these prefixes. Very inconsistent.

At best this is useless and noisey that you have to parse because the
information is also in the stack trace.

At worse these are misleading because they're highlighting something
internal (like validateDOMNesting) which even suggests an internal bug.
Even the ones public to React aren't necessarily what you called because
you might be calling a wrapper around it.

That would be properly reflected in a stack trace - which can also
properly ignore list so that the first stack you see is your callsite,

Which might be like `render()` in react-testing-library rather than
`createRoot()` for example.
2024-02-23 15:16:54 -05:00
Sebastian Markbåge
c0274063f0 [Flight] Prefix Replayed Console Logs with a Badge (#28403)
Builds on top of #28384.

This prefixes each log with a badge similar to how we badge built-ins
like "ForwardRef" and "Memo" in the React DevTools. The idea is that we
can add such badges in DevTools for Server Components too to carry on
the consistency.

This puts the "environment" name in the badge which defaults to
"Server". So you know which source it is coming from.

We try to use the same styling as the React DevTools. We use light-dark
mode where available to support the two different color styles, but if
it's not available I use a fixed background so that it's always readable
even in dark mode.

In Terminals, instead of hard coding colors that might not look good
with some themes, I use the ANSI color code to flip
background/foreground colors in that case.

In earlier commits I had it on the end of the line similar to the
DevTools badges but for multiline I found it better to prefix it. We
could try various options tough.

In most cases we can use both ANSI and the `%c` CSS color specifier,
because node will only use ANSI and hide the other. Chrome supports both
but the color overrides ANSI if it comes later (and Chrome doesn't
support color inverting anyway). Safari/Firefox prints the ANSI, so it
can only use CSS colors.

Therefore in browser builds I exclude ANSI.

On the server I support both so if you use Chrome inspector on the
server, you get nice colors on both terminal and in the inspector.

Since Bun uses WebKit inspector and it prints the ANSI we can't safely
emit both there. However, we also can't emit just the color specifier
because then it prints in the terminal.
https://github.com/oven-sh/bun/issues/9021 So we just use a plain string
prefix for now with a bracket until that's fixed.

Screen shots:

<img width="758" alt="Screenshot 2024-02-21 at 12 56 02 AM"
src="https://github.com/facebook/react/assets/63648/4f887ffe-fffe-4402-bf2a-b7890986d60c">
<img width="759" alt="Screenshot 2024-02-21 at 12 56 24 AM"
src="https://github.com/facebook/react/assets/63648/f32d432f-f738-4872-a700-ea0a78e6c745">
<img width="514" alt="Screenshot 2024-02-21 at 12 57 10 AM"
src="https://github.com/facebook/react/assets/63648/205d2e82-75b7-4e2b-9d9c-aa9e2cbedf39">
<img width="489" alt="Screenshot 2024-02-21 at 12 57 34 AM"
src="https://github.com/facebook/react/assets/63648/ea52d1e4-b9fa-431d-ae9e-ccb87631f399">
<img width="516" alt="Screenshot 2024-02-21 at 12 58 23 AM"
src="https://github.com/facebook/react/assets/63648/52b50fac-bec0-471d-a457-1a10d8df9172">
<img width="956" alt="Screenshot 2024-02-21 at 12 58 56 AM"
src="https://github.com/facebook/react/assets/63648/0096ed61-5eff-4aa9-8a8a-2204e754bd1f">
2024-02-21 14:59:08 -05:00
Sebastian Markbåge
9a5b6bd84f [Flight] Instrument the Console in the RSC Environment and Replay Logs on the Client (#28384)
When developing in an RSC environment, you should be able to work in a
single environment as if it was a unified environment. With thrown
errors we already serialize them and then rethrow them on the client.

Since by default we log them via onError both in Flight and Fizz, you
can get the same log in the RSC runtime, the SSR runtime and on the
client.

With console logs made in SSR renders, you typically replay the same
code during hydration on the client. So for example warnings already
show up both in the SSR logs and on the client (although not guaranteed
to be the same). You could just spend your time in the client and you'd
be fine.

Previously, RSC logs would not be replayed because they don't hydrate.
So it's easy to miss warnings for example.

With this approach, we replay RSC logs both during SSR so they end up in
the SSR logs and on the client. That way you can just stay in the
browser window during normal development cycles. You shouldn't have to
care if your component is a server or client component when working on
logical things or iterating on a product.

With this change, you probably should mostly ignore the Flight log
stream and just look at the client or maybe the SSR one. Unless you're
digging into something specific. In particular if you just naively run
both Flight and Fizz in the same terminal you get duplicates. I like to
run out fixtures `yarn dev:region` and `yarn dev:global` in two separate
terminals.

Console logs may contain complex objects which can be inspected. Ideally
a DevTools inspector could reach into the RSC server and remotely
inspect objects using the remote inspection protocol. That way complex
objects can be loaded on demand as you expand into them. However, that
is a complex environment to set up and the server might not even be
alive anymore by the time you inspect the objects. Therefore, I do a
best effort to serialize the objects using the RSC protocol but limit
the depth that can be rendered.

This feature is only own in dev mode since it can be expensive.

In a follow up, I'll give the logs a special styling treatment to
clearly differentiate them from logs coming from the client. As well as
deal with stacks.
2024-02-21 14:47:55 -05:00
Sebastian Markbåge
4ea424e63d Capitalize the default Flight environment (#28402)
It's cleaner and more in line with how we style other badges like "Memo"
and "ForwardRef" in DevTools.
2024-02-20 22:30:23 -05:00