## Overview
Branched off https://github.com/facebook/react/pull/28130
### ~Failing~ Fixed by @eps1lon
Most of the tests pass, but there are 3 tests that have additional
warnings due to client render error retries.
For example, before we would log:
```
Warning: Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.
Warning: Expected server HTML to contain a matching text node for "0" in <div>.
```
And now we log
```
Warning: Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.
Warning: Expected server HTML to contain a matching text node for "0" in <div>.
Warning: Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.
```
We can't just update the expected error count for these tests, because
the additional error only happens on the client. So I need some guidance
on how to fix these.
---------
Co-authored-by: Sebastian Silbermann <sebastian.silbermann@klarna.com>
## Overview
Branched off https://github.com/facebook/react/pull/28130
## ~Failing~ Fixed by @eps1lon
The tests are currently failing because of two tests covering special
characters. I've tried a few ways to fix, but I'm stuck and will need
some help understanding why they fail and how to fix.
---------
Co-authored-by: Sebastian Silbermann <sebastian.silbermann@klarna.com>
## Overview
Branched off https://github.com/facebook/react/pull/28130
In `hydrateRoot`, we now error if you pass `undefined`:
```
Warning: Must provide initial children as second argument to hydrateRoot.
```
So we expect 1 error for this now.
## Overview
Branched off https://github.com/facebook/react/pull/28130
## React for count changing
### Before
These tests are weird because on main they pass, but log to the console:
```
We expected 2 warning(s), but saw 1 warning(s).
We saw these warnings:
Warning: Expected server HTML to contain a matching <select> in <div>.
at select
```
The other one is ignored. The `expect(console.errors).toBeCalledWith(2)`
doesn't account for ignored calls, so the test passes with the two
expected (the +1 is in the test utiles). The ignored warning is
```
Warning: ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead.
```
So the mismatch is in the ignored warnings.
### After
After switching to `createRoot`, it still logs:
```
We expected 2 warning(s), but saw 1 warning(s).
We saw these warnings:
Warning: Expected server HTML to contain a matching <select> in <div>.
at select
```
But the test fails due to an unexpected error count. The new ignored
errors are:
```
Error: Uncaught [Error: Hydration failed because the initial UI does not match what was rendered on the server.]
Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.
Error: Hydration failed because the initial UI does not match what was rendered on the server.
Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
```
These seem to be the correct warnings to fire in `createRoot`, so the
fix is to update the number of warnings we expect.
## Overview
Adds support for `ReactDOMClient` for `ServerIntegration*` tests.
Also converts tests that pass without any other changes. Will follow up
with other PRs for more complex cases.
There's no need to separate strict mode from strict effects mode any
more.
I didn't clean up the `StrictEffectMode` fiber flag, because it's used
to prevent strict effects in legacy mode. I could replace those checks
with `LegacyMode` checks, but when we remove legacy mode, we can remove
that flag and condense them into one StrictMode flag away.
Starting in version 19, users can import the `act` testing API from the
`react` package instead of using a renderer specific API, like
`react-dom/test-utils`.
Semantically if you make your reason for aborting a Postpone instance
the render should not hit the error pathways but should instead follow
the postpone pathways. It's awkward today to actually get your hands on
a Postpone instance because you have to catch the throw from postpone
and then pass that into `abort()` or `AbortController.abort()`
(depending on the renderer API you are using)
This change makes it so that in most circumstances if you abort with a
postpone the `onPostpone` handler will be called and the Suspense
boundaries still pending will be put into client render mode with the
appropriate postpone digest to avoid trigger recoverable error pathways
on the client.
Similar to postponing in the shell during a resume or render however if
you abort before the shell is complete in a resume or render we will
fatally error. The fatal error is contextualized by React to avoid
passing the postpone object itself to the `onError` and related options.
Treat `<a href="" />` the same with and without
`enableFilterEmptyStringAttributesDOM`
in https://github.com/facebook/react/pull/18513 we started to warn and
ignore for empty `href` and `src` props since it usually hinted at a
mistake. However, for anchor tags there's a valid use case since `<a
href=""></a>` will by spec render a link to the current page. It could
be used to reload the page without having to rely on browser
affordances.
The implementation for Fizz is in the spirit of
https://github.com/facebook/react/pull/21153. I gated the fork behind
the flag so that the fork is DCE'd when the flag is off.
Updates Fizz to handle Hoistables (Resources and Elements) in a way that
better aligns with Suspense fallbacks
1. Hoistable Elements inside a fallback (regardless of how deep and how
many additional boundaries are intermediate) will be ignored. The
reasoning is fallbacks are transient and since there is not good way to
clean up hoistables because they escape their Suspense container its
better to not emit them in the first place. SSR fallbacks are already
not full fidelity because they never hydrate so this aligns with that
somewhat.
2. Hoistable stylesheets in fallbacks will only block the reveal of a
parent suspense boundary if the fallback is going to flush with that
completed parent suspense boundary. Previously if you rendered a
stylesheet Resource inside a fallback any parent suspense boundaries
that completed after the shell flushed would include that resource in
the set required to resolve before the boundary reveal happens on the
client. This is not a semantic change, just a performance optimization
3. preconnect and preload hoistable queues are gone, if you want to
optimize resource loading you shoudl use `ReactDOM.preconnect` and
`ReactDOM.preload`. `viewport` meta tags get their own queue because
they need to go before any preloads since they affect the media state.
In addition to those functional changes this PR also refactors the
boundary resource tracking by moving it to the task rather than using
function calls at the start of each render and flush. Tasks also now
track whether they are a fallback task
supercedes prior work here: https://github.com/facebook/react/pull/27534
Follow-up to
https://github.com/facebook/react/pull/28139#discussion_r1468852457
I mistakenly kept the tests using comment nodes as containers as legacy
tests. It's not that comments nodes aren't allowed in createRoot
entirely. Only behind `disableCommentsAsDOMContainers`. We already had
one test following that pattern so I just applied the same pattern to
the other tests for consistency.
Now `DOMPluginEventSystem` no longer uses any legacy roots.
Each it block here was duplicated to cover ReactDOM.render and
ReactDOMClient.createRoot. Here we delete the ReactDOM.render coverage.
Co-authored-by: Jack Pope <jackpope@meta.com>
## Overview
These tests are important for `ReactDOM.render`, so instead of just
re-writing them to `createRoot` and losing coverage:
- Moved the `.render` tests to `ReactLegacyUpdates`
- Re-wrote the tests in `ReactUpdates` to use `createRoot`
- Remove `unstable_batchedUpdates` from `ReactUpdates`
In a future PR, when I flag `batchedUpdates` with a Noop, I can add the
gate to just the tests in `ReactLegacyUpdates`.
To convert this file, I started replacing all the calls in line, and
quickly realized that we already have most of these tests covered in
other files. So I found the test that we didn't already have for
`create/hydrateRoot` and added them, then renamed `ReactMount` to
`ReactLegacyMount`.
Convert ReactServerRenderingHydration-test to createRoot (partially)
Some tests seem to be specifically testing the legacy APIs, maybe we
need to keep those around. Keeping this PR to the simple updates.
The internal file ReactSharedSubset is what the `react` module resolves
to when imported from a Server Component environment. We gave it this
name because, originally, the idea was that Server Components can access
a subset of the APIs available on the client.
However, since then, we've also added APIs that can _only_ by accessed
on the server and not the client. In other words, it's no longer a
subset, it's a slightly different overlapping set.
So this commit renames ReactSharedSubet to ReactServer and updates all
the references. This does not affect the public API, only our internal
implementation.
Upgrade ReactDOMShorthandCSSPropertyCollision-test to createRoot
Using the codemod from #27921 as a starting point, this migrates the
test to `createRoot`.
If we end up client rendering a boundary due to an error after we have
already injected a postponed hole in that boundary we'll end up trying
to target a missing segment. Since we never insert segments for an
already errored boundary into the HTML. Normally an errored prerender
wouldn't be used but if it is, such as if it was an intentional client
error it triggers this case. Those should really be replaced with
postpones though.
This is a bit annoying since we eagerly build up the postponed path. I
took the easy route here and just cleared out the suspense boundary
itself from having any postponed slots. However, this still creates an
unnecessary replay path along the way to the boundary. We could probably
walk the path and remove any empty parent nodes.
What is worse is that if this is the only thing that postponed, we'd
still generate a postponed state even though there's actually nothing to
resume. Since this is a bit of an edge case already maybe it's fine.
In my test I added a check for the `error` event on `window` since this
error only surfaces by throwing an ignored error. We should really do
that globally for all tests. Our tests should fail by default if there's
an error logged to the window.
Fixes a bug in the experimental `initialValue` option for
`useDeferredValue` (added in #27500).
If rendering the `initialValue` causes the tree to suspend, React should
skip it and switch to rendering the final value instead. It should not
wait for `initialValue` to resolve.
This is not just an optimization, because in some cases the initial
value may _never_ resolve — intentionally. For example, if the
application does not provide an instant fallback state. This capability
is, in fact, the primary motivation for the `initialValue` API.
I mostly implemented this correctly in the original PR, but I missed
some cases where it wasn't working:
- If there's no Suspense boundary between the `useDeferredValue` hook
and the component that suspends, and we're not in the shell of the
transition (i.e. there's a parent Suspense boundary wrapping the
`useDeferredValue` hook), the deferred task would get incorrectly
dropped.
- Similarly, if there's no Suspense boundary between the
`useDeferredValue` hook and the component that suspends, and we're
rendering a synchronous update, the deferred task would get incorrectly
dropped.
What these cases have in common is that it causes the `useDeferredValue`
hook itself to be replaced by a Suspense fallback. The fix was the same
for both. (It already worked in cases where there's no Suspense fallback
at all, because those are handled differently, at the root.)
The way I discovered this was when investigating a particular bug in
Next.js that would happen during a 'popstate' transition (back/forward),
but not during a regular navigation. That's because we render popstate
transitions synchronously to preserve browser's scroll position — which
in this case triggered the second scenario above.
Historically React would produce component stacks for dev builds only.
There is a cost to tracking component stacks and given the prod builds
try to optimize runtime performance these stacks were left out. More
recently React added production component stacks to Fiber in because it
can be immensely helpful in tracking down hard to debug production
issues. Fizz was not updated to have a similar behavior.
With the advent of prerendering however stacks for production in Fizz
are more relevant because prerendering is not really a dev-time task. If
you want the ability to reason about errors or postpones that happen
during a prerender having component stacks to interrogate is helpful and
these component stacks need to be available in production otherwise you
are really never going to see them. (it is possible that you could do
dev-mode prerenders but we don't expect this to be a common dev mode
workflow)
To better support the prerender use case and to make error logging in
Fizz more useful the following changes have been made
1. `onPostpone` now accepts a second `postponeInfo` argument which will
contain a componentStack. Postpones always originate from a component
render so the stack should be consistently available. The type however
will indicate the stack is optional so we can remove them in the future
if we decide the overhead is the wrong tradeoff in certain cases
2. `onError` now accepts a second `errorInfo` argument which may contain
a componentStack. If an error originated from a component a stack will
be included in the following cases.
This change entails tracking the component hierarchy in prod builds now.
While this isn't cost free it is implemented in a relatively lean
manner. Deferring the most expensive work (reifying the stack) until we
are actually in an error pathway.
In the course of implementing this change a number of simplifications
were made to the code which should make the stack tracking more
resilient. We no longer use a module global to curry the stack up to
some handler. This was delicate because you needed to always reset it
properly. We now curry the stack on the task itself.
Another change made was to track the component stack on SuspenseBoundary
instances so that we can provide the stack when aborting suspense
boundaries to help you determine which ones were affected by an abort.
Small test similar to few tests added in #27740 , the `reactDom-input`
error message was just modified to match the error message, and the
`reactDomTextarea-test.js` has tests added to ensure more coverage.
As part of the process of removing the deprecated `react-dom/test-utils`
package references to `act` from this module are replaced with
references to `unstable_act` in `react`. It is likely that the unstable
act implementation will be made stable. The test utils act is just a
reexport of the unstable_act implementation in react itself.
`onHeaders` can throw however for now we can assume that headers are
optimistic values since the only things we produce for them are preload
links. This is a pragmatic decision because React could concievably have
headers in the future which were not optimistic and thus non-optional
however it is hard to imagine what these headers might be in practice.
If we need to change this behavior to be fatal in the future it would be
a breaking change.
This commit adds error logging when `onHeaders` throws and ensures the
request can continue to render successfully.
When we postpone during a render we inject a new segment synchronously
which we postpone. That gets assigned an ID so we can refer to it
immediately in the postponed state.
When we do that, the parent segment may complete later even though it's
also synchronous. If that ends up not having any content in it, it'll
inline into the child and that will override the child's segment id
which is not correct since it was already assigned one.
To fix this, we simply opt-out of the optimization in that case which is
unfortunate because we'll generate many more unnecessary empty segments.
So we should come up with a new strategy for segment id assignment but
this fixes the bug.
Co-authored-by: Josh Story <story@hey.com>
I introduced a bug in a recent change to how bootstrap scripts are
handled. Rather than clearing out the bootstrap script state from
ResumableState on completion of the prerender I did it during the
flushing phase which comes later after the postponed state has likely
been serialized. We should freeze these objects in dev so this is not
possible to do easily in test (nor in actual code in real systems).
This fixes the bug by eliminating the bootstrap config during
getPostponedState which is before the state can be serialized.
Previously it was possible to postpone in the shell during a prerender
and then during a resume the bootstrap scripts would not be emitted
leading to no hydration on the client. This change moves the bootstrap
configuration to `ResumableState` where it can be serialized after
postponing if it wasn't flushed as part of the static shell.
Adds a new option to `react-dom/server` entrypoints.
`onHeaders: (headers: Headers) => void` (non node envs)
`onHeaders: (headers: { Link?: string }) => void` (node envs)
When any `renderTo...` or `prerender...` function is called and this
option is provided the supplied function will be called sometime on or
before completion of the render with some preload link headers.
When provided during a `renderTo...` the callback will usually be called
after the first pass at work. The idea here is we want to get a set of
headers to start the browser loading well before the shell is ready. We
don't wait for the shell because if we did we may as well send the
preloads as tags in the HTML.
When provided during a `prerender...` the callback will be called after
the entire prerender is complete. The idea here is we are not responding
to a live request and it is preferable to capture as much as possible
for preloading as Headers in case the prerender was unable to finish the
shell.
Currently the following resources are always preloaded as headers when
the option is provided
1. prefetchDNS and preconnects
2. font preloads
3. high priority image preloads
Additionally if we are providing headers when the shell is incomplete
(regardless of whether it is render or prerender) we will also include
any stylesheet Resources (ones with a precedence prop)
There is a second option `maxHeadersLength?: number` which allows you to
specify the maximum length of the header content in unicode code units.
This is what you get when you read the length property of a string in
javascript. It's improtant to note that this is not the same as the
utf-8 byte length when these headers are serialized in a Response. The
utf8 representation may be the same size, or larger but it will never be
smaller.
If you do not supply a `maxHeadersLength` we defaul to `2000`. This was
chosen as half the value of the max headers length supported by commonly
known web servers and CDNs. many browser and web server can support
significantly more headers than this so you can use this option to
increase the headers limit. You can also of course use it to be even
more conservative. Again it is important to keep in mind there is no
direct translation between the max length and the bytelength and so if
you want to stay under a certain byte length you need to be potentially
more aggressive in the maxHeadersLength you choose.
Conceptually `onHeaders` could be called more than once as new headers
are discovered however if we haven't started flushing yet but since most
APIs for the server including the web standard Response only allow you
to set headers once the current implementation will only call it one
time
`Activity` is the current candidate name. This PR starts the rename work
by renaming the exported unstable component name.
NOTE: downstream consumers need to rename the import when updating to
this commit.
Updates useFormState to allow a sync function to be passed as an action.
A form action is almost always async, because it needs to talk to the
server. But since we support client-side actions, too, there's no reason
we can't allow sync actions, too.
I originally chose not to allow them to keep the implementation simpler
but it's not really that much more complicated because we already
support this for actions passed to startTransition. So now it's
consistent: anywhere an action is accepted, a sync client function is a
valid input.
The loading state tracking for suspensey CSS is too complicated. Prior
to this change it had a state it could enter into where a stylesheet was
already in the DOM but the loading state did not know it was inserted
causing a later transition to try to insert it again.
This fix is to add proper tracking of insertions on the codepaths that
were missing it. It also modifies the logic of when to suspend based on
whether the stylesheet has already been inserted or not.
This is not 100% correct semantics however because a prior commit could
have inserted a stylesheet and a later transition should ideally be able
to wait on that load before committing. I haven't attempted to fix this
yet however because the loading state tracking is too complicated as it
is and requires a more thorough refactor. Additionally it's not
particularly valuable to delay a transition on a loading stylesheet when
a previous commit also relied on that stylesheet but didn't wait for it
b/c it was sync. I will follow up with an improvement PR later
fixes: https://github.com/facebook/react/issues/27585
I neglected to update the "last" pointer of the action queue. Since the
queue is circular, rather than dropping the update, the effect was to
add the update to the front of the queue instead of the back. I didn't
notice earlier because in my demos/tests, the actions would either
resolve really quickly or the actions weren't order dependent (like
incrementing a counter).
When we postpone a prerender in the shell, we should just leave an empty
prelude and resume from the root. While preserving any options passed
in.
Since we haven't flushed anything we can't assume we've already emitted
html/body tags or any resources tracked in the resumable state. This
introduces a resetResumableState function to reset anything we didn't
flush.
This is a bit hacky. Ideally, we probably shouldn't have tracked it as
already happened until it flushed or something like that.
Basically, it's like restarting the prerender with the same options and
then immediately aborting. When we add the preload headers, we'd track
those as preload() being emitted after the reset and so they get readded
to the resumable state in that case.
By default, partial hydration is given the lowest possible priority,
because until a tree is updated, the server-rendered HTML is assumed to
match the final resolved HTML.
However, this isn't completely true because a component may choose to
"upgrade" itself upon hydration. The simplest example is a component
that calls setState in a useEffect to switch to a richer implementation
of the UI. Another example is a component that doesn't have a server-
rendered implementation, so it intentionally suspends to force a client-
only render.
useDeferredValue is an example, too: the server only renders the first
pass (the initialValue) argument, and relies on the client to upgrade to
the final value.
What we should really do in these cases is emit some information into
the Fizz stream so that Fiber knows to prioritize the hydration of
certain trees. We plan to add a mechanism for this in the future.
In the meantime, though, we can at least ensure that the priority of the
upgrade task is correct once it's "discovered" during hydration. In this
case, the priority of the task spawned by useDeferredValue should have
Transition priority, not Offscreen priority.
During a popstate event, we attempt to render updates synchronously even
if they are transitions, to preserve scroll position if possible. We do
this by entangling the transition lane with the Sync lane.
However, if rendering the transition spawns additional transition
updates (e.g. a setState inside useEffect), there's no reason to render
those synchronously, too. We should use the normal transition behavior.
This fixes an issue where useDeferredValue during a popstate event would
spawn a transition update that was itself also synchronous.
In https://github.com/facebook/react/pull/27541 I added tests to assert
that the write after close bug was fixed. However the Node
implementation has an abort behavior preventing reproduction of the
original issue and the Browser build does not use AsyncLocalStorage
which also prevents reproduction. This change moves the Browser test to
a Edge runtime where AsyncLocalStorage is polyfilled. In this
implementation the test does correctly fail when the patch is removed.
Float methods can hang on to a reference to a Request after the request
is closed due to AsyncLocalStorage. If a Float method is called at this
point we do not want to attempt to flush anything. This change updates
the closing logic to also call `stopFlowing` which will ensure that any
checks against the destination properly reflect that we cannot do any
writes. In addition it updates the enqueueFlush logic to existence check
the destination inside the work function since it can change across the
work scheduling gap if it is async.
fixes: https://github.com/facebook/react/issues/27540
<!--
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
Changes `before before` to `before` in error messages.
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
I want to improve error logs
## 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 small refactor to how the lane entanglement mechanism works. We can
now distinguish between the lane that "spawned" a render task (i.e. a
new update) versus the lanes that it's entangled with. Both the update
lane and the entangled lanes will be included while rendering, but by
keeping them separate, we don't lose the original priority.
In practical terms, this means we can now entangle a low priority update
with a higher priority lane while rendering at the lower priority.
To do this, lanes that are entangled at the root are now tracked using
the same variable that we use to track the "base lanes" when revealing a
previously hidden tree — conceptually, they are the same thing. I also
renamed this variable (from subtreeLanes to entangledRenderLanes) to
better reflect how it's used.
My primary motivation is related to useDeferredValue, which I'll address
in a later PR.
This is the same problem as we had with keyPath before where if the
element itself suspends, we have to restore the replay node to what it
was before, however, if something below the element suspends we
shouldn't pop it because that will pop it back up the stack.
Instead of passing replay as an argument to every renderElement
function, I use a hack to compare if the node is still the same as the
one we tried to render, then that means we haven't stepped down into the
child yet. Maybe this is not quite correct because in theory you could
have a recursive node that just renders itself over and over until some
context bails out.
This solves an issue where if you suspended in an element it would retry
trying to replay from that element but using the postponed state from
the root.
The `enableCustomElementPropertySupport` flag changes React's handling
of custom elements in a way that is more useful that just treating every
prop as an attribute. However when server rendering we have no choice
but to serialize props as attributes. When this flag is on and React
supports more prop types on the client like functions and objects the
server implementation needs to be a bit more naunced in how it renders
these components. With this flag on `false`, function, and object props
are omitted entirely and `true` is normalized to `""`. There was a bug
however in the implementation which caused children more complex than a
single string to be omitted because it matched the object type filter.
This change reorganizes the code a bit to put these filters in the
default prop handline case, leaving children, style, and innerHTML to be
handled via normal logic.
fixes: https://github.com/facebook/react/issues/27286
Adds a second argument to useDeferredValue called initialValue:
```js
const value = useDeferredValue(finalValue, initialValue);
```
During the initial render of a component, useDeferredValue will return
initialValue. Once that render finishes, it will spawn an additional
render to switch to finalValue.
This same sequence should occur whenever the hook is hidden and revealed
again, i.e. by a Suspense or Activity, though this part is not yet
implemented.
When initialValue is not provided, useDeferredValue has no effect during
initial render, but during an update, it will remain on the previous
value, then spawn an additional render to switch to the new value. (This
is the same behavior that exists today.)
During SSR, initialValue is always used, if provided.
This feature is currently behind an experimental flag. We plan to ship
it in a non-breaking release.
The way we collect component stacks right now are pretty fragile.
We expect that we'll call captureBoundaryErrorDetailsDev whenever an
error happens. That resets lastBoundaryErrorComponentStackDev to null
but if we don't, it just lingers and we don't set it to anything new
then which leaks the previous component stack into the next time we have
an error. So we need to reset it in a bunch of places.
This is still broken with erroredReplay because it has the inverse
problem that abortRemainingReplayNodes can call
captureBoundaryErrorDetailsDev more than one time. So the second
boundary won't get a stack.
We probably should try to figure out an alternative way to carry along
the stack. Perhaps WeakMap keyed by the error object.
This also fixes an issue where we weren't invoking the onShellReady
event if we error a replay. That event is a bit weird for resuming
because we're probably really supposed to just invoke it immediately if
we have already flushed the shell in the prerender which is always atm.
Right now, it gets invoked later than necessary because you could have a
resumed hole ready before a sibling in the shell is ready and that's
blocked.
Upgrades the stability of Server Actions from experimental to canary.
- Turns on enableAsyncActions and enableFormActions
- Removes "experimental_" prefix from useOptimistic, useFormStatus, and
useFormState
Fixes whatever part of https://github.com/facebook/react/issues/26876
and https://github.com/vercel/next.js/issues/49499 that
https://github.com/facebook/react/pull/27394 didn't fix, probably.
From manual tests I believe this behavior brings us back to parity with
latest stable release (18.2.0). It's awkward that we keep the user's
state even for controlled inputs, so the DOM is out of sync with React
state.
Previously the .defaultChecked assignment done in updateInput() was
changing the actual checkedness because the dirty flag wasn't getting
set, meaning that hydrating could change which radio button is checked,
even in the absence of user interaction! Now we go back to always
detaching again.
Fixes#26876 for real?
In 18.2.0 (last stable), we set .checked unconditionally:
https://github.com/facebook/react/blob/v18.2.0/packages/react-dom/src/client/ReactDOMInput.js#L129-L135
This is important because if we are updating two radios' checkedness
from (false, true) to (true, false), we need to make sure that
input2.checked is explicitly set to false, even though setting
`input1.checked = true` already unchecks input2.
I think this fix is not complete because there is no guarantee that all
the inputs rerender at the same time? Hence the TODO. But in practice
they usually would and I _think_ this is comparable to what we had
before.
Also treating function and symbol as false like we used to and like we
do on initial mount.
The `resumeElement` function wasn't actually doing the correct thing
because it was resuming the element itself but what the child slot means
is that we're supposed to resume in the direct child of the element.
This is difficult to check for since it's all the possible branches that
the element can render into, so instead we just check this in
renderNode. It means the hottest path always checks the task which is
unfortunate.
And also, resuming using the correct nextSegmentId.
Fixes two bugs surfaced by this test.
---------
Co-authored-by: Josh Story <josh.c.story@gmail.com>
This adds a regression test and fix for a case where a sync update
triggers selective hydration, which then leads to a "Maximum update
depth exceeded" error, even though there was only a single update. This
happens when a single sync update flows into many sibling dehydrated
Suspense boundaries.
This fix is, if a commit was the result of selective hydration, we
should not increment the nested update count, because those renders
conceptually are not updates.
Ideally, they wouldn't even be in a separate commit — we should be able
to hydrate a tree and apply an update on top of it within the same
render phase. We could do this once we implement resumable context
stacks.
As agreed, we're removing Server Context. This was never official
documented.
We've found that it's not that useful in practice. Often the better
options are:
- Read things off the url or global scope like params or cookies.
- Use the module system for global dependency injection.
- Use `React.cache()` to dedupe multiple things instead of computing
once and passing down.
There are still legit use cases for Server Context but you have to be
very careful not to pass any large data, so in generally we recommend
against it anyway.
Yes, prop drilling is annoying but it's not impossible for the cases
this is needed. I would personally always pick it over Server Context
anyway.
Semantically, Server Context also blocks object deduping due to how it
plays out with Server Components that can't be deduped. This is much
more important feature.
Since it's already in canary along with the rest of RSC, we're adding a
warning for a few versions before removing completely to help migration.
---------
Co-authored-by: Josh Story <josh.c.story@gmail.com>
Currently when we SSR a Flight response we do not emit any resources for
module imports. This means that when the client hydrates it won't have
already loaded the necessary scripts to satisfy the Imports defined in
the Flight payload which will lead to a delay in hydration completing.
This change updates `react-server-dom-webpack` and
`react-server-dom-esm` to emit async script tags in the head when we
encounter a modules in the flight response.
To support this we need some additional server configuration. We need to
know the path prefix for chunk loading and whether the chunks will load
with CORS or not (and if so with what configuration).
Refactors Resources to have a more compact and memory efficient
struture. Resources generally are just an Array of chunks. A resource is
flushed when it's chunks is length zero. A resource does not have any
other state.
Stylesheets and Style tags are different and have been modeled as a unit
as a StyleQueue. This object stores the style rules to flush as part of
style tags using precedence as well as all the stylesheets associated
with the precedence. Stylesheets still need to track state because it
affects how we issue boundary completion instructions. Additionally
stylesheets encode chunks lazily because we may never write them as html
if they are discovered late.
The preload props transfer is now maximally compact (only stores the
props we would ever actually adopt) and only stores props for
stylesheets and scripts because other preloads have no resource
counterpart to adopt props into. The ResumableState maps that track
which keys have been observed are being overloaded. Previously if a key
was found it meant that a resource already exists (either in this render
or in a prior prerender). Now we discriminate between null and object
values. If map value is null we can assume the resource exists but if it
is an object that represents a prior preload for that resource and the
resource must still be constructed.
This fixes so that you can postpone in a fallback. This postpones the
parent boundary. I track the fallbacks in a separate replay node so that
when we resume, we can replay the fallback itself and finish the
fallback and then possibly later the content. By doing this we also
ensure we don't complete the parent too early since now it has a render
task on it.
There is one case that this surfaces that isn't limited to
prerender/resume but also render/hydrateRoot. I left todos in the tests
for this.
If you postpone in a fallback, and suspend in the content but eventually
don't postpone in the content then we should be able to just skip
postponing since the content rendered and we no longer need the
fallback. This is a bit of a weird edge case though since fallbacks are
supposed to be very minimal.
This happens because in both cases the fallback starts rendering early
as soon as the content suspends. This also ensures that the parent
doesn't complete early by increasing the blocking tasks. Unfortunately,
the fallback will irreversibly postpone its parent boundary as soon as
it hits a postpone.
When you suspend, the same thing happens but we typically deal with this
by doing a "soft" abort on the fallback since we don't need it anymore
which unblocks the parent boundary. We can't do that with postpone right
now though since it's considered a terminal state.
I think I'll just leave this as is for now since it's an edge case but
it's an annoying exception in the model. Makes me feel I haven't quite
nailed it just yet.
If we track a postponed SuspenseBoundary parent that we have to replay
through before it has postponed and it postpones itself later, we need
to upgrade it to a postponed replay boundary instead of adding two.
We currently abort a stream either it's explicitly told to abort (e.g.
by an abortsignal). In this case we still finish writing what we have as
well as instructions for the client about what happened so it can
trigger fallback cases and log appropriately.
We also abort a request if the stream itself cancels. E.g. if you can't
write anymore. In this case we should not write anything to the outgoing
stream since it's supposed to be closed already now. However, we should
still abort the request so that more work isn't performed and so that we
can log the reason for it to the onError callback.
We should also not do any work after aborting.
There we need to stop the "flow" of bytes - so I call stopFlowing in the
cancel case before aborting.
The tests were testing this case but we had changed the implementation
to only start flowing at initial read (pull) instead of start like we
used to. As a result, it was no longer covering this case. We have to
call reader.read() in the tests to start the flow so that we need to
cancel it.
We also were missing a final assertion on the error logs and since we
were tracking them explicitly the extra error was silenced.
Fixes#26876, I think. Review each commit separately (all assertions
pass in main already, except the last assertInputTrackingIsClean in
"should control radio buttons").
I'm actually a little confused on two things here:
* All the isCheckedDirty assertions are true. But I don't think we set
.checked unconditionally? So how does this happen?
* https://github.com/facebook/react/issues/26876#issuecomment-1611662862
claims that
d962f35ca...1f248bdd7 contains
the faulty change, but it doesn't appear to change the restoration logic
that I've touched here. (One difference outside restoration is that
updateProperties did previously set `.checked` when `nextProp !==
lastProp` whereas the new logic in updateInput is to set it when
`node.checked !== !!checked`.)
But it seems to me like we need this call here anyway, and if it fixes
it then it fixes it? I think technically speaking we probably should do
all the updateInput() calls and then all the updateValueIfChanged()
calls—in particular I think if clicking A changed the checked radio
button from B to C then the code as I have it would be incorrect, but
that also seems unlikely so idk whether to care.
cc @zhengjitf @Luk-z who did some investigation on the original issue
Moves writing queues to renderState.
We shouldn't need the resource tracking's value. We just need to know if
that resource has already been emitted. We can use a Set for this. To
ensure that set is directly serializable we can just use a
dictionary-like object with no value.
See individual commits for special cases.
Based on #27385.
When we error or abort during replay, that doesn't actually error the
component that errored because that has already rendered. The error only
affects any child that is not yet completed. Therefore the error kind of
gets thrown at the resumable point.
The resumable point might be a hole in the replay path, in which case
throwing there errors the parent boundary just the same as if the replay
component errored. If the hole is inside a deeper Suspense boundary
though, then it's that Suspense boundary that gets client rendered. I.e.
the child boundary. We can still finish any siblings.
In the shell all resumable points are inside a boundary since we must
have finished the shell. Therefore if you error in the root, we just
simply just turn all incomplete boundaries into client renders.
This forks Task into ReplayTask and RenderTask.
A RenderTask is the normal mode and it has a segment to write into.
A ReplayTask doesn't have a segment to write into because that has
already been written but instead it has a ReplayState which keeps track
of the next set of paths to follow. Once we hit a "Resume" node we
convert it into a RenderTask and continue rendering from there.
We can resume at either an Element position or a Slot position. An
Element pointing to a component doesn't mean we resume that component,
it means we resume in the child position directly below that component.
Slots are slots inside arrays.
Instead of statically forking most paths, I kept using the same path and
checked for the existence of a segment or replay state dynamically at
runtime.
However, there's still quite a bit of forking here like retryRenderTask
and retryReplayTask. Even in the new forks there's a lot of duplication
like resumeSuspenseBoundary, replaySuspenseBoundary and
renderSuspenseBoundary. There's opportunity to simplify this a bit.
Adds a separate entry point for the react-dom package when it's accessed
from a Server Component environment, using the "react-server" export
condition.
When you're inside a Server Component module, you won't be able to
import client-only APIs like useState. This applies to almost all React
DOM exports, except for Float ones like preload.
During an MPA form submission, useFormState should only reuse the form
state if same action is passed both times. (We also compare the key
paths.)
We compare the identity of the inner closure function, disregarding the
value of the bound arguments. That way you can pass an inline Server
Action closure:
```js
function FormContainer({maxLength}) {
function submitAction(prevState, formData) {
'use server'
if (formData.get('field').length > maxLength) {
return { errorMsg: 'Too many characters' };
}
// ...
}
return <Form submitAction={submitAction} />
}
```
If a Server Action is passed to useFormState, the action may be
submitted before it has hydrated. This will trigger a full page
(MPA-style) navigation. We can transfer the form state to the next page
by comparing the key path of the hook instance.
`ReactServerDOMServer.decodeFormState` is used by the server to extract
the form state from the submitted action. This value can then be passed
as an option when rendering the new page. It must be passed during both
SSR and hydration.
```js
const boundAction = await decodeAction(formData, serverManifest);
const result = await boundAction();
const formState = decodeFormState(result, formData, serverManifest);
// SSR
const response = createFromReadableStream(<App />);
const ssrStream = await renderToReadableStream(response, { formState })
// Hydration
hydrateRoot(container, <App />, { formState });
```
If the `formState` option is omitted, then the state won't be
transferred to the next page. However, it must be passed in both places,
or in neither; misconfiguring will result in a hydration mismatch.
(The `formState` option is currently prefixed with `experimental_`)
There's a subtle difference if you suspend before the first array or
after. In Fiber, we don't deal with this because we just suspend the
parent and replay it if lazy() or Usable are used in its child slots. In
Fizz we try to optimize this a bit more and enable resuming inside the
component.
Semantically, it's different if you suspend/postpone before the first
child array or inside that child array. Because when you resume the
inner result might be another array and either that's part of the parent
path or part of the inner slot.
There might be more clever way of structuring this but I just use -1 to
indicate that we're not yet inside the array and is in the root child
position. If that renders an element, then that's just the same as the 0
slot.
We need to also encode this in the resuming. I called that resuming the
element or resuming the slot.
When Float was first developed the internal implementation and external
interface were the same. This is problematic for a few reasons. One, the
public interface is typed but it is also untrusted and we should not
assume that it is actually respected. Two, the internal implementations
can get called from places other than the the public interface and
having to construct an options argument that ends up being destructured
to process the request is computationally wasteful and may limit JIT
optimizations to some degree. Lastly, the wire format was not as
compressed as it could be and it was untyped.
This refactor aims to address that by separating the public interface
from the internal implementations so we can solve these challenges and
also make it easier to change Float in the future
* The internal dispatcher method preinit is now preinitStyle and
preinitScript.
* The internal dispatcher method preinitModule is now
preinitModuleScript in anticipation of different implementations for
other module types in the future.
* The wire format is explicitly typed and only includes options if they
are actually used omitting undefined and nulls.
* Some function arguments are not options even if they are optional. For
instance precedence can be null/undefined because we deafult it to
'default' however we don't cosnider this an option because it is not
something we transparently apply as props to the underlying instance.
* Fixes a problem with keying images in flight where srcset and sizes
were not being taken into account.
* Moves argument validation into the ReactDOMFloat file where it is
shared with all runtimes that expose these methods
* Fixes crossOrigin serialization to use empty string except when
'use-credentials'
It's possible to postpone a specific node and not using a wrapper
component. Therefore we encode the resumable slot as the index slot.
When it's a plain client component that postpones, it's encoded as the
child slot inside that component which is the one that's postponed
rather than the component itself.
Since it's possible for a child slot to suspend (e.g. React.lazy's
microtask in this case) retryTask might need to keep its index around
when it resolves.
Found a hydration bug that happens when you pass a Server Action to
`formAction` and the next node is a text instance.
The HTML generated by Fizz is something like this:
```html
<button name="$ACTION_REF_5" formAction="" formEncType="multipart/form-data" formMethod="POST">
<input type="hidden" name="$ACTION_5:0" value="..."/>
<input type="hidden" name="$ACTION_5:1" value="..."/>
<input type="hidden" name="$ACTION_KEY" value="..."/>Count: <!-- -->0
</button>
```
Fiber is supposed to skip over the extra hidden inputs, but it doesn't
handle this correctly if the next expected node isn't a host instance.
In this case, it's a text instance.
Not sure if the proper fix is to change the HTML that is generated, or
to change the hydration logic, but in this PR I've done the latter.
When implementing `preloadModule` and `preinitModule` these methods were
not exposed on the server rendering stub when they should have been.
This PR corrects that omission.
A planned feature of useFormState is that if the page load is the result
of an MPA-style form submission — i.e. a form was submitted before it
was hydrated, using Server Actions — the state of the hook should
transfer to the next page.
I haven't implemented that part yet, but as a prerequisite, we need some
way for Fizz to indicate whether a useFormState hook was rendered using
the "postback" state. That way we can do all state matching logic on the
server without having to replicate it on the client, too.
The approach here is to emit a comment node for each useFormState hook.
We use one of two comment types: `<!--F-->` for a normal useFormState
hook, and `<!--F!-->` for a hook that was rendered using the postback
state. React will read these markers during hydration. This is similar
to how we encode Suspense boundaries.
Again, the actual matching algorithm is not yet implemented — for now,
the "not matching" marker is always emitted.
We can optimize this further by not emitting any markers for a render
that is not the result of a form postback, which I'll do in subsequent
PRs.
img tags inside picture tags should not automatically be preloaded
because usually the img is a fallback. We will consider a more
comprehensive way of preloading picture tags which may require a
technique like using an inline script to construct the image in the
browser but for now we simply omit the preloads to avoid harming load
times by loading fallbacks.
Just moving some internal code around again.
I originally encoded what type of work using startRender vs
startPrerender. I had intended to do more forking of the work loop but
we've decided not to go with that strategy. It also turns out that
forking when we start working is actually too late because of a subtle
thing where you can call abort before work begins. Therefore it's
important that starting the work comes later.
This joins the static (prerender) builds with the server builds but
doesn't change the public entry points.
The idea of two separate bundles is that we'd have a specialized build
for Fizz just for the prerender that could do a lot more. However, in
practice the code is implemented with a dynamic check so it's in both.
It's also not a lot of code.
At the same time if you do have a set up that includes both the
prerender and the render in the same build output, this just doubles the
server bundle size for no reason.
So we might as well merge them into one build. However, I don't expose
the `prerender` from `/server`. Instead it's just exposed from the
public `/static` entry point. This leaves us with the option to go back
to separate builds later if it diverges more in the future.
This is basically the implementation for the prerender pass.
Instead of forking basically the whole implementation for prerender, I
just add a conditional field on the request. If it's `null` it behaves
like before. If it's non-`null` then instead of triggering client
rendered boundaries it triggers those into a "postponed" state which is
basically just a variant of "pending". It's supposed to be filled in
later.
It also builds up a serializable tree of which path can be followed to
find the holes. This is basically a reverse `KeyPath` tree.
It is unfortunate that this approach adds more code to the regular Fizz
builds but in practice. It seems like this side is not going to add much
code and we might instead just want to merge the builds so that it's
smaller when you have `prerender` and `resume` in the same bundle -
which I think will be common in practice.
This just implements the prerender side, and not the resume side, which
is why the tests have a TODO. That's in a follow up PR.
This implements useFormState in Fiber. (It does not include any
progressive enhancement features; those will be added later.)
useFormState is a hook for tracking state produced by async actions. It
has a signature similar to useReducer, but instead of a reducer, it
accepts an async action function.
```js
async function action(prevState, payload) {
// ..
}
const [state, dispatch] = useFormState(action, initialState)
```
Calling dispatch runs the async action and updates the state to the
returned value.
Async actions run before React's render cycle, so unlike reducers, they
can contain arbitrary side effects.
A transition that flows into a dehydrated boundary should not suspend if
the boundary is showing a fallback.
This is related to another issue where Fizz streams in the initial HTML
after a client navigation has already happened. That issue is not fixed
by this commit, but it does make it less likely. Need to think more
about the larger issue.
Import maps need to be emitted before any scripts or preloads so the
browser can properly locate these resources.
Unlike most scripts, importmaps are singletons meaning you can only have
one per document and they must appear before any modules are loaded or
preloaded. In the future there may be a way to dynamically add more
mappings however the proposed API for this seems likely to be a
javascript API and not an html tag.
Given the unique constraints here this PR implements React's support of
importMaps as the following
1. an `importMap` option accepting a plain object mapping module
specifier to path is accepted in any API that renders a preamble (head
content). Notably this precludes resume rendering because in resume
cases the preamble should have already been produced as part of the
prerender step.
2. the importMap is stringified and emitted as a `<script
type="importmap">...</script>` in the preamble.
3. the importMap is escaped identically to how bootstrapScriptContent is
escaped, notably, isntances of `</script>` are escaped to avoid breaking
out of the script context
Users can still render importmap tags however with Float enabled this is
rather pointless as most modules will be hoisted above the importmap
that is rendered. In practice this means the only functional way to use
import maps with React is to use this config API.
This exposes, but does not yet implement, a new experimental API called
useFormState. It's gated behind the enableAsyncActions flag.
useFormState has a similar signature to useReducer, except instead of a
reducer it accepts an (async) action function. React will wait until the
promise resolves before updating the state:
```js
async function action(prevState, payload) {
// ..
}
const [state, dispatch] = useFormState(action, initialState)
```
When used in combination with Server Actions, it will also support
progressive enhancement — a form that is submitted before it has
hydrated will have its state transferred to the next page. However, like
the other action-related hooks, it works with fully client-driven
actions, too.
This URL is generated on the client (there's an equivalent but shorter
SSR version too) when a function is used as an action. It should never
happen but it'll be invoked if a form is manually submitted or event is
stopped early.
The `'` wasn't escaped so this yielded invalid syntax. Which is an error
too but much less helpful. `missing ) after argument list`. Added a test
that evals to make sure it's correct syntax.
This exposes a `resume()` API to go with the `prerender()` (only in
experimental). It doesn't work yet since we don't yet emit the postponed
state so not yet tested.
The main thing this does is rename ResponseState->RenderState and
Resources->ResumableState. We separated out resources into a separate
concept preemptively since it seemed like separate enough but probably
doesn't warrant being a separate concept. The result is that we have a
per RenderState in the Config which is really just temporary state and
things that must be flushed completely in the prerender. Most things
should be ResumableState.
Most options are specified in the `prerender()` and transferred into the
`resume()` but certain options that are unique per request can't be.
Notably `nonce` is special. This means that bootstrap scripts and
external runtime can't use `nonce` in this mode. They need to have a CSP
configured to deal with external scripts, but not inline.
We need to be able to restore state of things that we've already emitted
in the prerender. We could have separate snapshot/restore methods that
does this work when it happens but that means we have to explicitly do
that work. This design is trying to keep to the principle that we just
work with resumable data structures instead so that we're designing for
it with every feature. It also makes restoring faster since it's just
straight into the data structure.
This is not yet a serializable format. That can be done in a follow up.
We also need to vet that each step makes sense. Notably stylesToHoist is
a bit unclear how it'll work.
renderToString is a legacy server API which used a trick to avoid having
the DOCTYPE included when rendering full documents by setting the root
formatcontext to HTML_MODE rather than ROOT_HTML_MODE. Previously this
was of little consequence but with Float the Root mode started to be
used for things like determining if we could flush hoistable elements
yet. In issue #27177 we see that hoisted elements can appear before the
<html> tag when using a legacy API `renderToString`.
This change exports a DOCTYPE from FizzConfigDOM and FizzConfigDOMLegacy
respectively, using an empty chunk in the legacy case. The only runtime
perf cost here is that for legacy APIs there is an extra empty chunk to
write when rendering a top level <html> tag which is trivial enough
Fixes#27177
This fixes the regression test added in the previous commit. The
"Suspensey commit" implementation relies on the
`shouldRemainOnPreviousScreen` function to determine whether to 1)
suspend the commit 2) activate a parent fallback and schedule a retry.
The issue was that we were sometimes attempting option 2 even when there
was no parent fallback.
Part of the reason this bug landed is due to how `throwException` is
structured. In the case of Suspensey commits, we pass a special "noop"
thenable to `throwException` as a way to trigger the Suspense path. This
special thenable must never have a listener attached to it. This is not
a great way to structure the logic, it's just a consequence of how the
code evolved over time. We should refactor it into multiple functions so
we can trigger a fallback directly without having to check the type. In
the meantime, I added an internal warning to help detect similar
mistakes in the future.
Adds a failing test for a case discovered by Next.js. An error boundary
is triggered during initial hydration, and the error fallback includes a
stylesheet. If the stylesheet has not yet been loaded, the commit
suspends, but never resolves even after the stylesheet finishes loading.
Triggering this bug depends on several very specific code paths being
triggered simultaneously. There are a few ways we could fix the bug;
I'll submit as one or more separate PRs to show that each one is
sufficient.
Stacked on #27224
### Implements `ReactDOM.preloadModule()`
`preloadModule` is a function to preload modules of various types.
Similar to `preload` this is useful when you expect to use a Resource
soon but can't render that resource directly. At the moment the only
sensible module to preload is script modules along with some other `as`
variants such as `as="serviceworker"`. In the future when there is some
notion of how to preload style module script or json module scripts this
API will be extended to support those as well.
##### Arguments
1. `href: string` -> the href or src value you want to preload.
2. `options?: {...}` ->
2.1. `options.as?: string` -> the `as` property the modulepreload link
should render with. If not provided it will be omitted which will cause
the modulepreload to be treated like a script module
2.2. `options.crossOrigin?: string` -> modules always load with CORS but
you can provide `use-credentials` if you want to change the default
behavior
2.3. `options.integrity?: string` -> an integrity hash for subresource
integrity APIs
##### Rendering
each preloaded module will emit a `<link rel="modulepreload" href="..."
/>`
if `as` is specified and is something other than `"script"` the as
attribute will also be included
if crossOrigin or integrity as specified their attributes will also be
included
During SSR these script tags will be emitted before content. If we have
not yet flushed the document head they will be emitted there after
things that block paint such as font preloads, img preloads, and
stylesheets.
On the client these link tags will be appended to the document.head.
### Implements `ReactDOM.preinitModule()`
`preinitModule` is a function to loading module scripts before they are
required. It has the same use cases as `preinit`.
During SSR you would use this to tell the browsers to start fetching
code that will be used without having to wait for bootstrapping to
initiate module fetches.
ON the client you would use this to start fetching a module script early
for an anticipated navigation or other event that is likely to depend on
this module script.
the `as` property for Float methods drew inspiration from the `as`
attribute of the `<link rel="preload" ... >` tag but it is used as a
sort of tag for the kind of thing being targetted by Float methods. For
`preinitModule` we currently only support `as: "script"` and this is
also the assumed default type so you current never need to specify this
`as` value. In the future `preinitModule` will support additional module
script types such as `style` or `json`. The support of these types will
correspond to [Module Import
Attributes](https://github.com/tc39/proposal-import-attributes).
##### Arguments
1. `href: string` -> the href or src value you want to preinitialize
2. `options?: {...}` ->
2.1 `options.as?: string` -> only supports `script` and this is the
default behavior. Until we support import attributes such as `json` and
`style` there will not be much reason to provide an `as` option.
2.2. `options.crossOrigin?: string`: modules always load with CORS but
you can provide `use-credentials` if you want to change the default
behavior
2.3 `options.integrity?: string` -> an integrity hash for subresource
integrity APIs
##### Rendering
each preinitialized `script` module will emit a `<script type="module"
async="" src"...">` During SSR these will appear behind display blocking
resources such as font preloads, img preloads, and stylesheets. In the
browser these will be appende to the head.
Note that for other `as` types the rendered output will be slightly
different. `<script type="module">import "..." with {type: "json"
}</script>`. Since this tag is an inline script variants of React that
do not use inline scripts will simply omit these preinitialization tags
from the SSR output. This is not implemented in this PR but will appear
in a future update.
This adds an experimental `unstable_postpone(reason)` API.
Currently we don't have a way to model effectively an Infinite Promise.
I.e. something that suspends but never resolves. The reason this is
useful is because you might have something else that unblocks it later.
E.g. by updating in place later, or by client rendering.
On the client this works to model as an Infinite Promise (in fact,
that's what this implementation does). However, in Fizz and Flight that
doesn't work because the stream needs to end at some point. We don't
have any way of knowing that we're suspended on infinite promises. It's
not enough to tag the promises because you could await those and thus
creating new promises. The only way we really have to signal this
through a series of indirections like async functions, is by throwing.
It's not 100% safe because these values can be caught but it's the best
we can do.
Effectively `postpone(reason)` behaves like a built-in [Catch
Boundary](https://github.com/facebook/react/pull/26854). It's like
`raise(Postpone, reason)` except it's built-in so it needs to be able to
be encoded and caught by Suspense boundaries.
In Flight and Fizz these behave pretty much the same as errors. Flight
just forwards it to retrigger on the client. In Fizz they just trigger
client rendering which itself might just postpone again or fill in the
value. The difference is how they get logged.
In Flight and Fizz they log to `onPostpone(reason)` instead of
`onError(error)`. This log is meant to help find deopts on the server
like finding places where you fall back to client rendering. The reason
that you pass in is for that purpose to help the reason for any deopts.
I do track the stack trace in DEV but I don't currently expose it to
`onPostpone`. This seems like a limitation. It might be better to expose
the Postpone object which is an Error object but that's more of an
implementation detail. I could also pass it as a second argument.
On the client after hydration they don't get passed to
`onRecoverableError`. There's no global `onPostpone` API to capture
postponed things on the client just like there's no `onError`. At that
point it's just assumed to be intentional. It doesn't have any `digest`
or reason passed to the client since it's not logged.
There are some hacky solutions that currently just tries to reuse as
much of the existing code as possible but should be more properly
implemented.
- Fiber is currently just converting it to a fake Promise object so that
it behaves like an infinite Promise.
- Fizz is encoding the magic digest string `"POSTPONE"` in the HTML so
we know to ignore it but it should probably just be something neater
that doesn't share namespace with digests.
Next I plan on using this in the `/static` entry points for additional
features.
Why "postpone"? It's basically a synonym to "defer" but we plan on using
"defer" for other purposes and it's overloaded anyway.
stacked on #27222
When I initially developed Float I was trying to be extremely clever in
how to explain when there are mismatching Resource instances. I still
think that we should do some kind of validation here but I want to
implement something much simpler. In practice there are not many cases
where you would accidentally create the same resource twice but with
differing props. Since I am going to land `preloadModule` and
`preinitModule` soon and I want to avoid adding to the overly complex
dev validation there I am going to remove it now and add something later
that is simplified.
Data URI's in images can't effectively be preloaded (the URI contains
the data so preloading only duplicates the data in the stream. If we
encounter an image with this protocol in the src attribute we should
avoid preloading it.
imageSrcSet should have been srcSet when referencing an img tag.
imageSizes should have been sizes. This caused preloads for img tags
using srcSet and sizes to incorrectly render as having a href only,
dropping the srcSet and sizes part of the preload
Eventually we will treat images without `loading="lazy"` as suspensey
meaning we will coordinate the reveal of boundaries when these images
have loaded and ideally decoded. As a step in that direction this change
prioritizes these images for preloading to ensure the highest chance
that they are loaded before boundaries reveal (or initial paint). every
img rendered that is non lazy loading will emit a preload just behind
fonts.
This change implements a new resource queue for high priority image
preloads
There are a number of scenarios where we end up putting a preload in
this queue
1. If you render a non-lazy image and there are fewer than 10 high
priority image preloads
2. if you render a non-lazy image with fetchPriority "high"
3. if you preload as "image" with fetchPriority "high"
This means that by default we won't overrsaturate this queue with every
img rendered on the page but the earlier encountered ones will go first.
Essentially this is React's own implementation of fetchPriority="auto".
If however you specify that the fetchPriority is higher then in theory
an unlimited number of images can preload in this queue. This gives
users some control over queuing while still providing a good default
that does not require any opting into
Additionally we use fetchPriority "low" as a signal that an image does
not require preloading. This may end up being pointless if not using
lazy (which also opts out of preloading) because it might delay initial
paint but we'll start with this hueristic and consider changes in the
future when we have more information
Currently React attempts to prioritize certain preloads over others
based on their type. This is at odds with allowing the user to control
priority by ordering which calls are made first. There are some asset
types that generally should just be prioritized first such as fonts
since we don't know when fonts will be used and they either block
display or may lead to fallback fonts being used. But for scripts and
stylesheets we can emit them in the order received with other arbitrary
preload types.
We will eventually add support for emitting suspensey image preloads
before other resources because these also block display however that
implementation will look at which images are actually rendered rather
than simply preloaded.
Generally scripts should not be preloaded before images but if they
arrive earlier than image preloads (or images) the network (or server)
may be saturated responding to inflight script preloads and not
sufficiently prioritize images arriving later. This change marks the
preloaded bootstrap script with a `low` fetch priority to signal to
supporting browsers that the request should be deprioritized. This
should make the preload operate similar to async script fetch priority
which is low by default according to https://web.dev/fetch-priority/
Additionally the bootstrap script preloads will emit before
preinitialized scripts do. Normal script preloads will continue to be
prioritized after stylesheets
This change can land separatrely but is part of a larger effort to
implement elevating image loading and making script loading less
blocking. Later changes will emit used suspensey images earlier in the
queue and will stop favoring scripts over images that are explicitly
preloaded
## Summary
as we began [discussing
yesterday](https://github.com/facebook/react/pull/27056#discussion_r1253282784),
`SuspenseList` is not actually stable yet, and should likely be exported
with the `unstable_` prefix.
the conversation yesterday began discussing this in the context of the
fb-specific packages, but changing it there without updating everywhere
else leads to test failures, so here the change is made across packages.
## How did you test this change?
```
yarn flow dom-browser
yarn test
```
`unstable_batchedUpdates` shouldn't really be called on the server but
the semantics are clear enough and we can provide a trivial
implementation that calls the provided callback so we are adding it to
the server-rendering-stub.
This means we will also keep it around when we make the top level
react-dom exports match the server-rendering-stub in the next major
This PR contains a regression test and two separate fixes: a targeted
fix, and a more general one that's designed as a last-resort guard
against these types of bugs (both bugs in app code and bugs in React).
I confirmed that each of these fixes separately are sufficient to fix
the regression test I added.
We can't realistically detect all infinite update loop scenarios because
they could be async; even a single microtask can foil our attempts to
detect a cycle. But this improves our strategy for detecting the most
common kind.
See commit messages for more details.
## Summary
This PR cleans up `useMutableSource`. This has been blocked by a
remaining dependency internally at Meta, but that has now been deleted.
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
## How did you test this change?
```
yarn flow
yarn lint
yarn test --prod
```
<!--
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.
-->
When we diffInCommitPhase there's no updatePayload, which caused no
update to be applied.
This is unfortunate because it would've been a lot easier to see this
oversight if we didn't have to support both flags.
I also carified that updateHostComponent is unnecessary in the new flag.
We reuse updateHostComponent for HostSingleton and HostHoistables since
it has a somewhat complex path but that means you have to remember when
editing updateHostComponent that it's not just used for that tag.
Luckily with the new flag, this is actually unnecessary since we just
need to mark it for update if any props have changed and then we diff it
later.
For float methods the href argument is usually all we need to uniquely
key the request. However when preloading responsive images it is
possible that you may need more than one preload for differing
imagesizes attributes. When using imagesrcset for preloads the href
attribute acts more like a fallback href. For keying purposes the
imagesrcset becomes the primary key conceptually.
This change updates the keying logic for `ReactDOM.preload()` when you
pass `{as: "image"}`
1. If `options.imageSrcSet` is a non-emtpy string the key is defined as
`options.imageSrcSet + options.imageSizes`. The `href` argument is still
required but does not participate in keying.
2. If `options.imageSrcSet` is empty, missing, or an invalid format the
key is defined as the `href`. Changing the `options.imageSizes` does not
affect the key as this option is inert when not using
`options.imageSrcSet`
Additionally, currently there is a bug in webkit (Safari) that causes
preload links to fail to use imageSrcSet and fallback to href even when
the browser will correctly resolve srcset on an `<img>` tag. Because the
drawbacks of preloading the wrong image (href over imagesrcset) in a
modern browser outweight the drawbacks of not preloading anything for
responsive images in browsers that do not support srcset at all we will
omit the `href` attribute whenever `options.imageSrcSet` is provided. We
still require you provide an href since we want to be able to revert
this behavior once all major browsers support it
bug link: https://bugs.webkit.org/show_bug.cgi?id=231150
Some browsers, with some CSP configuration, will not preload a script if
the prelaod link tag does not provide a valid nonce attribute. This
change adds the ability to specify a nonce for `ReactDOM.preload(..., {
as: "script" })`
Float types are currently spread out. this moves them to a single place
to ensure we properly handle the public type interface in all three
renderers.
This is a step towards moving the public interface and validation to a
common file shared by all three runtimes. Will also probably change the
function interface to be flatter
Currently we preload all scripts that are not hoisted. One of the
original reasons for this is we stopped SSR rendering async scripts that
had an onLoad/onError because we needed to be able to distinguish
between Float scripts and non-Float scripts during hydration. Hydration
has been refactored a bit and we can not get around this limitation so
we can just emit the async script in place. However, sync and defer
scripts are also preloaded. While this is sometimes desirable it is not
universally so and there are issues with conveying priority properly
(see fetchpriority) so with this change we remove the automatic
preloading of non-Float scripts altogether.
For this change to make sense we also need to emit async scripts with
loading handlers during SSR. we previously only preloaded them during
SSR because it was necessary to keep async scripts as unambiguously
resources when hydrating. One ancillary benefit was that load handlers
would always fire b/c there was no chance the script would run before
hydration. With this change we go back to having the ability to have
load handlers fired before hydration. This is already a problem with
images and we don't have a generalized solution for it however our
likely approach to this sort of thing where you need to wait for a
script to load is to use something akin to `importScripts()` rather than
rendering a script with onLoad.
We previously preloaded stylesheets that were rendered in Fizz. The idea
was we'd get a headstart fetching these resources since we know they are
going to be rendered. However to really be effective non-float
stylesheets need to rendered in the head and the preload here is not
helpful and potentially hurtful to perf in a minor way. This change
removes this functionality to make the code smaller and simpler
## Overview
Does a few things:
- Renames `enableSyncDefaultUpdates` to
`forceConcurrentByDefaultForTesting`
- Changes the way it's used so it's dead-code eliminated separate from
`allowConcurrentByDefault`
- Deletes a bunch of the gated code
The gates that are deleted are unnecessary now. We were keeping them
when we originally thought we would come back to being concurrent by
default. But we've shifted and now sync-by default is the desired
behavior long term, so there's no need to keep all these forked tests
around.
I'll follow up to delete more of the forked behavior if possible.
Ideally we wouldn't need this flag even if we're still using
`allowConcurrentByDefault`.
stacked on #26753
Adds support for preloading bootstrapModules. We don't yet support
modules in Float's public interface but this implementation should be
compatible with what we do when we add it.
This PR adds a preload for bootstrapScripts. preloads are captured
synchronously when you create a new Request and as such the normal logic
to check if a preload already exists is skipped.
clearContainer and clearSingleton both assumed scripts could be safely
removed from the DOM because normally once a script has been inserted
into the DOM it is executable and removing it, even synchronously, will
not prevent it from running. However There is an edge case in a couple
browsers (Chrome at least) where during HTML streaming if a script is
opened and not yet closed the script will be inserted into the document
but not yet executed. If the script is removed from the document before
the end tag is parsed then the script will not run. This change causes
clearContainer and clearSingleton to retain script elements. This is
generally thought to be safe because if we are calling these methods we
are no longer hydrating the container or the singleton and the scripts
execution will happen regardless.
This solves an issue where if you inject a hidden field in the beginning
of the form, we might mistakenly hydrate the injected one that was part
of an action.
I'm not too happy about how specific this becomes. It's similar to Float
but in general we don't do this deep comparison.
See https://github.com/vercel/next.js/issues/50087
Currently when React generates rel=preload link tags for script/stylesheet resources, it will not carryover nonce and fetchpriority values if specified on the original elements.
This change ensures that the preload links use the nonce and fetchPriority values if they were specified.
…affiliates.
## Summary
There were 8 different places where the copyright comment was wrong.
Rewrote from "Copyright (c) Facebook, Inc. and its affiliates." to
"Copyright (c) Meta Platforms, Inc. and its affiliates."
## How did you test this change?
No code was changed. Comment was still a comment after changes.
Co-authored-by: Dennis Moradkhani <denmo530@student.liu.se>
Previously, we'd call and use getSnapshot on the second render resulting
in `Warning: Text content did not match. Server: "Nay!" Client: "Yay!"`
and then `Error: Text content does not match server-rendered HTML.`.
Fixes#26095. Closes#26113. Closes#25650.
---------
Co-authored-by: eps1lon <silbermann.sebastian@gmail.com>
Stacked on top of #26735.
This allows a framework to add a `$$FORM_ACTION` property to a function.
This lets the framework return a set of props to use in place of the
function but only during SSR. Effectively, this lets you implement
progressive enhancement of form actions using some other way instead of
relying on the replay feature.
This will be used by RSC on Server References automatically by
convention in a follow up, but this mechanism can also be used by other
frameworks/libraries.
This allows us to emit extra ephemeral data that will only be used on
server rendered forms.
First I refactored the shouldSkip functions to now just do that work
inside the canHydrate methods. This makes the Config bindings a little
less surface area but it also helps us optimize a bit since we now can
look at the code together and find shared paths.
canHydrate returns the instance if it matches, that used to just be
there to refine the type but it can also be used to just return a
different instance later that we find. If we don't find one, we'll bail
out and error regardless so no need to skip past anything.
in https://github.com/facebook/react/pull/26738 we added nonce to the
ResponseState. Initially it was used in a variety of places but the
version that got merged only included it with the external fizz runtime.
This PR updates the config for the external fizz runtime so that the
nonce is encoded into the script chunks at request creation time.
The rationale is that for live-requests, streaming is more likely than
not so doing the encoding work at the start is better than during flush.
For cases such as SSG where the runtime is not required the extra
encoding is tolerable (not a live request). Bots are an interesting case
because if you want fastest TTFB you will end up requiring the runtime
but if you are withholding until the stream is done you have already
sacrificed fastest TTFB and the marginal slowdown of the extraneous
encoding is hopefully neglibible
I'm writing this so later if we learn that this tradeoff isn't worth it
we at least understand why I made the change in the first place.
This adds an experimental hook tentatively called useOptimisticState.
(The actual name needs some bikeshedding.)
The headline feature is that you can use it to implement optimistic
updates. If you set some optimistic state during a transition/action,
the state will be automatically reverted once the transition completes.
Another feature is that the optimistic updates will be continually
rebased on top of the latest state.
It's easiest to explain with examples; we'll publish documentation as
the API gets closer to stabilizing. See tests for now.
Technically the use cases for this hook are broader than just optimistic
updates; you could use it implement any sort of "pending" state, such as
the ones exposed by useTransition and useFormStatus. But we expect
people will most often reach for this hook to implement the optimistic
update pattern; simpler cases are covered by those other hooks.
Currently there is no way to provide a nonce when using
`ReactDOM.preinit(..., { as: 'script' })`
This PR adds `nonce?: string` as an option
While implementing this PR I added a test to also show you can pass
`integrity`. This test isn't directly related to the nonce change.
This hook reads the status of its ancestor form component, if it exists.
```js
const {pending, data, action, method} = useFormStatus();
```
It can be used to implement a loading indicator, for example. You can
think of it as a shortcut for implementing a loading state with the
useTransition hook.
For now, it's only available in the experimental channel. We'll share
docs once its closer to being stable. There are additional APIs that
will ship alongside it.
Internally it's implemented using startTransition + a context object.
That's a good way to think about its behavior, but the actual
implementation details may change in the future.
Because form elements cannot be nested, the implementation in the
reconciler does not bother to keep track of multiple nested "transition
providers". So although it's implemented using generic Fiber config
methods, it does currently make some assumptions based on React DOM's
requirements.
I found a couple scenarios where preloads were issued too aggressively
1. During SSR, if you render a new stylesheet after the preamble flushed
it will flush a preload even if the resource was already preloaded
2. During Client render, if you call `ReactDOM.preload()` it will only
check if a preload exists in the Document before inserting a new one. It
should check for an underlying resource such as a stylesheet link or
script if the preload is for a recognized asset type
We used to have Event Replaying for any kind of Discrete event where
we'd track any event after hydrateRoot and before the async code/data
has loaded in to hydrate the target. However, this didn't really work
out because code inside event handlers are expected to be able to
synchronously read the state of the world at the time they're invoked.
If we replay discrete events later, the mutable state around them like
selection or form state etc. may have changed.
This limitation doesn't apply to Client Actions:
- They're expected to be async functions that themselves work
asynchronously. They're conceptually also in the "navigation" events
that happen after the "submit" events so they're already not
synchronously even before the first `await`.
- They're expected to operate mostly on the FormData as input which we
can snapshot at the time of the event.
This PR adds a bit of inline script to the Fizz runtime (or external
runtime) to track any early submit events on the page - but only if the
action URL is our placeholder `javascript:` URL. We track a queue of
these on `document.$$reactFormReplay`. Then we replay them in order as
they get hydrated and we get a handle on the Client Action function.
I add the runtime to the `bootstrapScripts` phase in Fizz which is
really technically a little too late, because on a large page, it might
take a while to get to that script even if you have displayed the form.
However, that's also true for external runtime. So there's a very short
window we might miss an event but it's good enough and better than
risking blocking display on this script.
The main thing that makes the replaying difficult to reason about is
that we can have multiple instance of React using this same queue. This
would be very usual but you could have two different Reacts SSR:ing
different parts of the tree and using around the same version. We don't
have any coordinating ids for this. We could stash something on the form
perhaps but given our current structure it's more difficult to get to
the form instance in the commit phase and a naive solution wouldn't
preserve ordering between forms.
This solution isn't 100% guaranteed to preserve ordering between
different React instances neither but should be in order within one
instance which is the common case.
The hard part is that we don't know what instance something will belong
to until it hydrates. So to solve that I keep everything in the original
queue while we wait, so that ordering is preserved until we know which
instance it'll go into. I ended up doing a bunch of clever tricks to
make this work. These could use a lot more tests than I have right now.
Another thing that's tricky is that you can update the action before
it's replayed but we actually want to invoke the old action if that
happens. So we have to extract it even if we can't invoke it right now
just so we get the one that was there during hydration.
We currently use rollup to make an adhoc bundle from the file system
when we're testing an import of an external file.
This doesn't follow all the interception rules that we use in jest and
in our actual builds.
This switches to just using jest require() to load these. This means
that they effectively have to load into the global document so this only
works with global document tests which is all we have now anyway.
This wires up, but does not yet implement, an experimental hook called
useFormStatus. The hook is imported from React DOM, not React, because
it represents DOM-specific state — its return type includes FormData as
one of its fields. Other renderers that implement similar methods would
use their own renderer-specific types.
The API is prefixed and only available in the experimental channel.
It can only be used from client (browser, SSR) components, not Server
Components.
Insert temporary input node to polyfill submitter argument in FormData.
This works for buttons too and fixes a bug where the type attribute
wasn't reset.
I also exclude the submitter if it's a function action. This ensures
that we don't include the generated "name" when the action is a server
action. Conceptually that name doesn't exist.
Fizz can emit whatever it wants for the SSR version of these fields when
it's a function action so they might not align with what is in the
previous props. Therefore we need to force them to update if we're
updating to a non-function where they might be relevant again.
Stacked on #26557
Supporting Float methods such as ReactDOM.preload() are challenging for
flight because it does not have an easy means to convey direct
executions in other environments. Because the flight wire format is a
JSON-like serialization that is expected to be rendered it currently
only describes renderable elements. We need a way to convey a function
invocation that gets run in the context of the client environment
whether that is Fizz or Fiber.
Fiber is somewhat straightforward because the HostDispatcher is always
active and we can just have the FlightClient dispatch the serialized
directive.
Fizz is much more challenging becaue the dispatcher is always scoped but
the specific request the dispatch belongs to is not readily available.
Environments that support AsyncLocalStorage (or in the future
AsyncContext) we will use this to be able to resolve directives in Fizz
to the appropriate Request. For other environments directives will be
elided. Right now this is pragmatic and non-breaking because all
directives are opportunistic and non-critical. If this changes in the
future we will need to reconsider how widespread support for async
context tracking is.
For Flight, if AsyncLocalStorage is available Float methods can be
called before and after await points and be expected to work. If
AsyncLocalStorage is not available float methods called in the sync
phase of a component render will be captured but anything after an await
point will be a noop. If a float call is dropped in this manner a DEV
warning should help you realize your code may need to be modified.
This PR also introduces a way for resources (Fizz) and hints (Flight) to
flush even if there is not active task being worked on. This will help
when Float methods are called in between async points within a function
execution but the task is blocked on the entire function finishing.
This PR also introduces deduping of Hints in Flight using the same
resource keys used in Fizz. This will help shrink payload sizes when the
same hint is attempted to emit over and over again
This is the next step toward full support for async form actions.
Errors thrown inside form actions should cause the form to re-render and
throw the error so it can be captured by an error boundary. The behavior
is the same if the `<form />` had an internal useTransition hook, which
is pretty much exactly how we implement it, too.
The first time an action is called, the form's HostComponent is
"upgraded" to become stateful, by lazily mounting a list of hooks. The
rest of the implementation for function components can be shared.
Because the error handling behavior added in this commit is just using
useTransition under-the-hood, it also handles pending states, too.
However, this pending state can't be observed until we add a new hook
for that purpose. I'll add this next.
Stacked on #26570
Previously we restricted Float methods to only being callable while
rendering. This allowed us to make associations between calls and their
position in the DOM tree, for instance hoisting preinitialized styles
into a ShadowRoot or an iframe Document.
When considering how we are going to support Flight support in Float
however it became clear that this restriction would lead to compromises
on the implementation because the Flight client does not execute within
the context of a client render. We want to be able to disaptch Float
directives coming from Flight as soon as possible and this requires
being able to call them outside of render.
this patch modifies Float so that its methods are callable anywhere. The
main consequence of this change is Float will always use the Document
the renderer script is running within as the HoistableRoot. This means
if you preinit as style inside a component render targeting a ShadowRoot
the style will load in the ownerDocument not the ShadowRoot. Practially
speaking it means that preinit is not useful inside ShadowRoots and
iframes.
This tradeoff was deemed acceptable because these methods are
optimistic, not critical. Additionally, the other methods, preconntect,
prefetchDNS, and preload, are not impacted because they already operated
at the level of the ownerDocument and really only interface with the
Network cache layer.
I added a couple additional fixes that were necessary for getting tests
to pass that are worth considering separately.
The first commit improves the diff for `waitForThrow` so it compares
strings if possible.
The second commit makes invokeGuardedCallback not use metaprogramming
pattern and swallows any novel errors produced from trying to run the
guarded callback. Swallowing may not be the best we can do but it at
least protects React against rapid failure when something causes the
dispatchEvent to throw.
In anticipation of making Fiber use the document global for dispatching
Float methods that arrive from Flight I needed to update some tests that
commonly recreated the JSDOM instance after importing react.
This change updates a few tests to only create JSDOM once per test,
before importing react-dom/client.
Additionally the current act implementation for server streaming did not
adequately model streaming semantics so I rewrite the act implementation
in a way that better mirrors how a browser would parse incoming HTML.
The new act implementation does the following
1. the first time it processes meaningful streamed content it figures
out whether it is rendering into the existing document container or if
it needs to reset the document. this is based on whether the streamed
content contains tags `<html>` or `<body>` etc...
2. Once the streaming container is set it will typically continue to
stream into that container for future calls to act. The exception is if
the streaming container is the `<head>` in which case it will switch to
streaming into the body once it receives a `<body>` tag.
This means for tests that render something like a `<div>...</div>` it
will naturally stream into the default `<div id="container">...` and for
tests that render a full document the HTML will parse like a real
browser would (with some very minor edge case differences)
I also refactored the way we move nodes from buffered content into the
document and execute any scripts we find. Previously we were using
window.eval and I switched this to just setting the external script
content as script text. Additionally the nonce logic is reworked to be a
bit simpler.
This lets you pass a function to `<form action={...}>` or `<button
formAction={...}>` or `<input type="submit formAction={...}>`. This will
behave basically like a `javascript:` URL except not quite implemented
that way. This is a convenience for the `onSubmit={e => {
e.preventDefault(); const fromData = new FormData(e.target); ... }`
pattern.
You can still implement a custom `onSubmit` handler and if it calls
`preventDefault`, it won't invoke the action, just like it would if you
used a full page form navigation or javascript urls. It behaves just
like a navigation and we might implement it with the Navigation API in
the future.
Currently this is just a synchronous function but in a follow up this
will accept async functions, handle pending states and handle errors.
This is implemented by setting `javascript:` URLs, but these only exist
to trigger an error message if something goes wrong instead of
navigating away. Like if you called `stopPropagation` to prevent React
from handling it or if you called `form.submit()` instead of
`form.requestSubmit()` which by-passes the `submit` event. If CSP is
used to ban `javascript:` urls, those will trigger errors when these
URLs are invoked which would be a different error message but it's still
there to notify the user that something went wrong in the plumbing.
Next up is improving the SSR state with action replaying and progressive
enhancement.
I accidentally made a behavior change in the refactor. It turns out that
when switching off `checked` to an uncontrolled component, we used to
revert to the concept of "initialChecked" which used to be stored on
state.
When there's a diff to this computed prop and the value of props.checked
is null, then we end up in a case where it sets `checked` to
`initialChecked`:
5cbe6258bc/packages/react-dom-bindings/src/client/ReactDOMInput.js (L69)
Since we never changed `initialChecked` and it's not relevant if
non-null `checked` changes value, the only way this "change" could
trigger was if we move from having `checked` to having null.
This wasn't really consistent with how `value` works, where we instead
leave the current value in place regardless. So this is a "bug fix" that
changes `checked` to be consistent with `value` and just leave the
current value in place. This case should already have a warning in it
regardless since it's going from controlled to uncontrolled.
Related to that, there was also another issue observed in
https://github.com/facebook/react/pull/26596#discussion_r1162295872 and
https://github.com/facebook/react/pull/26588
We need to atomically apply mutations on radio buttons. I fixed this by
setting the name to empty before doing mutations to value/checked/type
in updateInput, and then set the name to whatever it should be. Setting
the name is what ends up atomically applying the changes.
---------
Co-authored-by: Sophie Alpert <git@sophiebits.com>
Since this is an observable behavior and is hard to think about, seems
good to have tests for this.
The expected value included in each test is the behavior that existed
prior to #26546.
In
2019ddc75f,
we changed to set .defaultValue before .value on updates. In some cases,
setting .defaultValue causes .value to change, and since we only set
.value if it has the wrong value, this resulted in us not assigning to
.value, which resulted in inputValueTracking not knowing the right
value. See new test added.
My fix here is to (a) move the value setting back up first and (b)
narrowing the fix in the aforementioned PR to newly remove the value
attribute only if it defaultValue was previously present in props.
The second half is necessary because for types where the value property
and attribute are indelibly linked (hidden checkbox radio submit image
reset button, i.e. spec modes default or default/on from
https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default),
we can't remove the value attribute after setting .value, because that
will undo the assignment we just did! That is, not having (b) makes all
of those types fail to handle updating props.value.
This code is incredibly hard to think about but I think this is right
(or at least, as right as the old code was) because we set .value here
only if the nextProps.value != null, and we now remove defaultValue only
if lastProps.defaultValue != null. These can't happen at the same time
because we have long warned if value and defaultValue are simultaneously
specified, and also if a component switches between controlled and
uncontrolled.
Also, it fixes the test in https://github.com/facebook/react/pull/26626.
<!--
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 debug-test --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
Browsers restore state like forms and scroll position right after the
popstate event. To make sure the page work as expected on back or
forward button, we need to flush transitions scheduled in a popstate
synchronously, and only yields if it suspends.
This PR adds a new HostConfig method to check if `window.event ===
'popstate'`, and `scheduleMicrotask` if a transition is scheduled in a
`PopStateEvent`.
## How did you test this change?
yarn test
Many of our Suspense-related tests were written before the `act` API was
introduced, and use the lower level `waitFor` helpers instead. So they
are less resilient to changes in implementation details than they could
be.
This converts some of our test suite to use `act` in more places. I
found these while working on a PR to expand our fallback throttling
mechanism to include all renders that result from a promise resolving,
even if there are no more fallbacks in the tree.
I think this covers all the remaining tests that are affected.
This traces back to https://github.com/facebook/react/pull/6449 and then
another before that.
I think that back then we favored the property over the attribute, and
setting the property wouldn't be enough. However, the default path for
these are now using attributes if we don't special case it. So we don't
need it.
The only difference is that we currently have a divergence for
symbol/function behavior between controlled values that use the
getToStringValue helpers which treat them as empty string, where as
everywhere else they're treated as null/missing.
Since this comes with a warning and is a weird error case, it's probably
fine to change.