* Pass children to hydration root constructor
I already made this change for the concurrent root API in #23309. This
does the same thing for the legacy API.
Doesn't change any behavior, but I will use this in the next steps.
* Add isRootDehydrated function
Currently this does nothing except read a boolean field, but I'm about
to change this logic.
Since this is accessed by React DOM, too, I put the function in a
separate module that can be deep imported. Previously, it was accessing
the FiberRoot directly. The reason it's a separate module is to break a
circular dependency between React DOM and the reconciler.
* Allow updates at lower pri without forcing client render
Currently, if a root is updated before the shell has finished hydrating
(for example, due to a top-level navigation), we immediately revert to
client rendering. This is rare because the root is expected is finish
quickly, but not exceedingly rare because the root may be suspended.
This adds support for updating the root without forcing a client render
as long as the update has lower priority than the initial hydration,
i.e. if the update is wrapped in startTransition.
To implement this, I had to do some refactoring. The main idea here is
to make it closer to how we implement hydration in Suspense boundaries:
- I moved isDehydrated from the shared FiberRoot object to the
HostRoot's state object.
- In the begin phase, I check if the root has received an by comparing
the new children to the initial children. If they are different, we
revert to client rendering, and set isDehydrated to false using a
derived state update (a la getDerivedStateFromProps).
- There are a few places where we used to set root.isDehydrated to false
as a way to force a client render. Instead, I set the ForceClientRender
flag on the root work-in-progress fiber.
- Whenever we fall back to client rendering, I log a recoverable error.
The overall code structure is almost identical to the corresponding
logic for Suspense components.
The reason this works is because if the update has lower priority than
the initial hydration, it won't be processed during the hydration
render, so the children will be the same.
We can go even further and allow updates at _higher_ priority (though
not sync) by implementing selective hydration at the root, like we do
for Suspense boundaries: interrupt the current render, attempt hydration
at slightly higher priority than the update, then continue rendering the
update. I haven't implemented this yet, but I've structured the code in
anticipation of adding this later.
* Wrap useMutableSource logic in feature flag
Currently, if a root is updated before the shell has finished hydrating
(for example, due to a top-level navigation), we immediately revert to
client rendering. This is rare because the root is expected is finish
quickly, but not exceedingly rare because the root may be suspended.
This adds support for updating the root without forcing a client render
as long as the update has lower priority than the initial hydration,
i.e. if the update is wrapped in startTransition.
To implement this, I had to do some refactoring. The main idea here is
to make it closer to how we implement hydration in Suspense boundaries:
- I moved isDehydrated from the shared FiberRoot object to the
HostRoot's state object.
- In the begin phase, I check if the root has received an by comparing
the new children to the initial children. If they are different, we
revert to client rendering, and set isDehydrated to false using a
derived state update (a la getDerivedStateFromProps).
- There are a few places where we used to set root.isDehydrated to false
as a way to force a client render. Instead, I set the ForceClientRender
flag on the root work-in-progress fiber.
- Whenever we fall back to client rendering, I log a recoverable error.
The overall code structure is almost identical to the corresponding
logic for Suspense components.
The reason this works is because if the update has lower priority than
the initial hydration, it won't be processed during the hydration
render, so the children will be the same.
We can go even further and allow updates at _higher_ priority (though
not sync) by implementing selective hydration at the root, like we do
for Suspense boundaries: interrupt the current render, attempt hydration
at slightly higher priority than the update, then continue rendering the
update. I haven't implemented this yet, but I've structured the code in
anticipation of adding this later.
Currently this does nothing except read a boolean field, but I'm about
to change this logic.
Since this is accessed by React DOM, too, I put the function in a
separate module that can be deep imported. Previously, it was accessing
the FiberRoot directly. The reason it's a separate module is to break a
circular dependency between React DOM and the reconciler.
I already made this change for the concurrent root API in #23309. This
does the same thing for the legacy API.
Doesn't change any behavior, but I will use this in the next steps.
* Flight side of server context
* 1 more test
* rm unused function
* flow+prettier
* flow again =)
* duplicate ReactServerContext across packages
* store default value when lazily initializing server context
* .
* better comment
* derp... missing import
* rm optional chaining
* missed feature flag
* React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ??
* add warning if non ServerContext passed into useServerContext
* pass context in as array of arrays
* make importServerContext nott pollute the global context state
* merge main
* remove useServerContext
* dont rely on object getters in ReactServerContext and disallow JSX
* add symbols to devtools + rename globalServerContextRegistry to just ContextRegistry
* gate test case as experimental
* feedback
* remove unions
* Lint
* fix oopsies (tests/lint/mismatching arguments/signatures
* lint again
* replace-fork
* remove extraneous change
* rebase
* 1 more test
* rm unused function
* flow+prettier
* flow again =)
* duplicate ReactServerContext across packages
* store default value when lazily initializing server context
* .
* better comment
* derp... missing import
* rm optional chaining
* missed feature flag
* React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ??
* add warning if non ServerContext passed into useServerContext
* pass context in as array of arrays
* make importServerContext nott pollute the global context state
* merge main
* remove useServerContext
* dont rely on object getters in ReactServerContext and disallow JSX
* add symbols to devtools + rename globalServerContextRegistry to just ContextRegistry
* gate test case as experimental
* feedback
* remove unions
* Lint
* fix oopsies (tests/lint/mismatching arguments/signatures
* lint again
* replace-fork
* remove extraneous change
* rebase
* reinline
* rebase
* add back changes lost due to rebase being hard
* emit chunk for provider
* remove case for React provider type
* update type for SomeChunk
* enable flag with experimental
* add missing types
* fix flow type
* missing type
* t: any
* revert extraneous type change
* better type
* better type
* feedback
* change import to type import
* test?
* test?
* remove react-dom
* remove react-native-renderer from react-server-native-relay/package.json
* gate change in FiberNewContext, getComponentNameFromType, use switch statement in FlightServer
* getComponentNameFromTpe: server context type gated and use displayName if available
* fallthrough
* lint....
* POP
* lint
This PR refactors the cache code by moving it out of ReactFiberCacheComponent to ReactFiberTransitionPool in anticipation of it being reused by multiple stacks (ie. transition tracing)
* Move createRoot/hydrateRoot to /client
We want these APIs ideally to be imported separately from things you
might use in arbitrary components (like flushSync). Those other methods
are "isomorphic" to how the ReactDOM tree is rendered. Similar to hooks.
E.g. importing flushSync into a component that only uses it on the client
should ideally not also pull in the entry client implementation on the
server.
This also creates a nicer parity with /server where the roots are in a
separate entry point.
Unfortunately, I can't quite do this yet because we have some legacy APIs
that we plan on removing (like findDOMNode) and we also haven't implemented
flushSync using a flag like startTransition does yet.
Another problem is that we currently encourage these APIs to be aliased by
/profiling (or unstable_testing). In the future you don't have to alias
them because you can just change your roots to just import those APIs and
they'll still work with the isomorphic forms. Although we might also just
use export conditions for them.
For that all to work, I went with a different strategy for now where the
real API is in / but it comes with a warning if you use it. If you instead
import /client it disables the warning in a wrapper. That means that if you
alias / then import /client that will inturn import the alias and it'll
just work.
In a future breaking changes (likely when we switch to ESM) we can just
remove createRoot/hydrateRoot from / and move away from the aliasing
strategy.
* Update tests to import from react-dom/client
* Fix fixtures
* Update warnings
* Add test for the warning
* Update devtools
* Change order of react-dom, react-dom/client alias
I think the order matters here. The first one takes precedence.
* Require react-dom through client so it can be aliased
Co-authored-by: Andrew Clark <git@andrewclark.io>
There are a few internal tests that still need to be updated, so I'm
adding this flag back for www only.
The desired behavior rolled out to 10% public, so we're confident there
are no issues.
The open source behavior remains (skipUnmountedBoundaries = true).
* Failing test for react#23331
* Don't warn on hydration mismatch if suspended
When something suspends during hydration, we continue rendering the
siblings to warm up the cache and fire off any lazy network requests.
However, if there are any mismatches while rendering the siblings, it's
likely a false positive caused by the earlier suspended component. So
we should suppress any hydration warnings until the tree no
longer suspends.
Fixes#23332
Co-authored-by: Marcel Laverdet <marcel@laverdet.com>
No id should be a subset of any other id. Currently, this is not true
when there are multiple hooks in the same component. We append the
hook index to the end of the id, except for the first one. So you get
this pattern.
Before this change:
- 1st hook's id: :R0:
- 2nd hook's id: :R0:1:
The first hook's id is a subset of all the other ids in the
same component.
The fix for this is to use a different character to separate the main
id from the hook index. I've chosen a captial 'H' for this because
capital letters are not part of the base 32 character set when encoding
with `toString(32)`.
After this change:
- 1st hook's id: :R0:
- 2nd hook's id: :R0H1:
The ids generated by useId are unique per React root. You can create
additional ids by concatenating them with locally unique strings.
To support this pattern, no id will ever be a subset of another id. We
achieve this by adding a special character to the beginning and end.
We use a colon (":") because it's uncommon — even if you don't prefix
the ids using the `identifierPrefix` option, collisions are unlikely.
One downside of a colon is that it's not a valid character in DOM
selectors, like `querySelectorAll`. We think this is probably
fine because it's not a common use case in React, and there are
workarounds or alternative solutions. But we're open to reconsidering
this in the future if there's a compelling argument.
* add transition name to startTransition
Add a transitionName to start transition, store the transition start time and name in the batch config, and pass it to the root on render
* Transition Tracing Types and Consts
* Root begin work
The root operates as a tracing marker that has all transitions on it. This PR only tested the root with one transition so far
- Store transitions in memoizedState. Do this in updateHostRoot AND attemptEarlyBailoutIfNoScheduledUpdate. We need to do this in the latter part because even if the root itself doesn't have an update, it could still have new transitions in its transitionLanes map that we need to process.
* Transition Tracing commit phase
- adds a module scoped pending transition callbacks object that contains all transition callbacks that have not yet been processed. This contains all callbacks before the next paint occurs.
- Add code in the mutation phase to:
* For the root, if there are transitions that were initialized during this commit in the root transition lanes map, add a transition start call to the pending transition callbacks object. Then, remove the transitions from the root transition lanes map.
* For roots, in the commit phase, add a transition complete call
We add this code in the mutation phase because we can't add it to the passive phase because then the paint might have occurred before we even know which callbacks to call
* Process Callbacks after paint
At the end of the commit phase, call scheduleTransitionCallbacks to schedule all pending transition callbacks to be called after paint. Then clear the callbacks
* Refactor warnForTextDifference
We're going to fork the behavior of this function between concurrent
roots and legacy roots.
The legacy behavior is to warn in dev when the text mismatches during
hydration. In concurrent roots, we'll log a recoverable error and revert
to client rendering. That means this is no longer a development-only
function — it affects the prod behavior, too.
I haven't changed any behavior in this commit. I only rearranged the
code slightly so that the dev environment check is inside the body
instead of around the function call. I also threaded through an
isConcurrentMode argument.
* Revert to client render on text content mismatch
Expands the behavior of enableClientRenderFallbackOnHydrationMismatch to
check text content, too.
If the text is different from what was rendered on the server, we will
recover the UI by falling back to client rendering, up to the nearest
Suspense boundary.
* Remove object-assign polyfill
We really rely on a more modern environment where this is typically
polyfilled anyway and we don't officially support IE with more extensive
polyfilling anyway. So all environments should have the native version
by now.
* Use shared/assign instead of Object.assign in code
This is so that we have one cached local instance in the bundle.
Ideally we should have a compile do this for us but we already follow
this pattern with hasOwnProperty, isArray, Object.is etc.
* Transform Object.assign to now use shared/assign
We need this to use the shared instance when Object.spread is used.
* fix getSnapshot warning when a selector returns NaN
useSyncExternalStoreWithSelector delegate a selector as
getSnapshot of useSyncExternalStore.
* Fiber's use sync external store has a same issue
* Small nits
We use Object.is to check whether the snapshot value has been updated,
so we should also use it to check whether the value is cached.
Co-authored-by: Andrew Clark <git@andrewclark.io>
We need to use the Offscreen Fiber's update queue for interaction tracing. This PR removes the optimization that React Cache uses to not need to push and pop the cache in special circumstances and defaults to always pushing and popping the cache as long as there was a previous cache.
Previously, ReactBatchConfig.transition was an number (1 = there is a transition, 0 = there isn't one). This PR changes this to a transition object (object = there is a transition, null = there isn't one) in preparation for transition tracing changes.
There are several cases where hydration fails, server-rendered HTML is
discarded, and we fall back to client rendering. Whenever this happens,
we will now log an error with onRecoverableError, with a message
explaining why.
In some of these scenarios, this is not the only recoverable error that
is logged. For example, an error during hydration will cause hydration
to fail, which is itself an error. So we end up logging two separate
errors: the original error, and one that explains why hydration failed.
I've made sure that the original error always gets logged first, to
preserve the causal sequence.
Another thing we could do is aggregate the errors with the Error "cause"
feature and AggregateError. Since these are new-ish features in
JavaScript, we'd need a fallback behavior. I'll leave this for a
follow up.
This code was originally added to force a client render after receiving
an error during hydration. Later we added the ForceClientRender to
implement the same behavior, but scoped to an individual Suspense
boundary instead of all the boundaries in the entire root. So it's
now redudant.
We had some test coverage already but I added another test specifically
for the case of throwing a recoverable hydration error in the shell.
If a hydration root receives an update before the outermost shell has
finished hydrating, we should give up hydrating and switch to
client rendering.
Since the shell is expected to commit quickly, this doesn't happen that
often. The most common sequence is something in the shell suspends, and
then the user quickly navigates to a different screen, triggering a
top-level update.
Instead of immediately switching to client rendering, we could first
attempt to hydration at higher priority, like we do for updates that
occur inside nested dehydrated trees.
But since this case is expected to be rare, and mainly only happens when
the shell is suspended, an attempt at higher priority would likely end
up suspending again anyway, so it would be wasted effort. Implementing
it this way would also require us to add a new lane especially for root
hydration. For simplicity's sake, we'll immediately switch to client
rendering. In the future, if we find another use case for a root
hydration lane, we'll reconsider.
* Allow suspending in the shell during hydration
Builds on behavior added in #23267.
Initial hydration should be allowed to suspend in the shell. In
practice, this happens because the code for the outer shell hasn't
loaded yet.
Currently if you try to do this, it errors because it expects there to
be a parent Suspense boundary, because without a fallback we can't
produce a consistent tree. However, for non-sync updates, we don't need
to produce a consistent tree immediately — we can delay the commit
until the data resolves.
In #23267, I added support for suspending without a parent boundary if
the update was wrapped with `startTransition`. Here, I've expanded this
to include hydration, too.
I wonder if we should expand this even further to include all non-sync/
discrete updates.
* Allow suspending in shell for all non-sync updates
Instead of erroring, we can delay the commit.
The only time we'll continue to error when there's no parent Suspense
boundary is during sync/discrete updates, because those are expected to
produce a complete tree synchronously to maintain consistency with
external state.
The `pooledCache` variable always points to either `root.pooledCache`
or the stack cursor that is used to track caches that were resumed from
a previous render. We can get rid of it by reading from those instead.
This simplifies the code a lot and is harder to mess up, I think.
- Add the type of transition tracing callbacks
- Add transition tracing callbacks as an option to `createRoot`
- Add transition tracing callbacks on the root
- Add option to pass transition tracing callbacks to createReactNoop
- Add Tracing Marker component type to React exports
- Add reconciler work tag
- Add devtools work tag
- Add boilerplate for the cache to render children
No functionality yet
(If the update is wrapped in startTransition)
Currently you're not allowed to suspend outside of a Suspense boundary.
We throw an error:
> A React component suspended while rendering, but no fallback UI
was specified
We treat this case like an error because discrete renders are expected
to finish synchronously to maintain consistency with external state.
However, during a concurrent transition (startTransition), what we can
do instead is treat this case like a refresh transition: suspend the
commit without showing a fallback.
The behavior is roughly as if there were a built-in Suspense boundary
at the root of the app with unstable_avoidThisFallback enabled.
Conceptually it's very similar because during hydration you're already
showing server-rendered UI; there's no need to replace that with
a fallback when something suspends.
I made a minor mistake in the original onRecoverableError PR that
only surfaces if there are hydration errors in two different Suspense
boundaries in the same render. This fixes it and adds a unit test.
Minor follow up to initial onRecoverableError PR.
When onRecoverableError is not provided to `createRoot`, the
renderer falls back to a default implementation. Originally I
implemented this with a host config method, but what we can do instead
is pass the default implementation the root constructor as if it were
a user provided one.
* Add .browser and .node explicit entry points
This can be useful when the automatic selection doesn't work properly.
* Remove react/index
I'm not sure why I added this in the first place. Perhaps due to how our
builds work somehow.
* Remove build-info.json from files field
* [RFC] Add onHydrationError option to hydrateRoot
This is not the final API but I'm pushing it for discussion purposes.
When an error is thrown during hydration, we fallback to client
rendering, without triggering an error boundary. This is good because,
in many cases, the UI will recover and the user won't even notice that
something has gone wrong behind the scenes.
However, we shouldn't recover from these errors silently, because the
underlying cause might be pretty serious. Server-client mismatches are
not supposed to happen, even if UI doesn't break from the users
perspective. Ignoring them could lead to worse problems later. De-opting
from server to client rendering could also be a significant performance
regression, depending on the scope of the UI it affects.
So we need a way to log when hydration errors occur.
This adds a new option for `hydrateRoot` called `onHydrationError`. It's
symmetrical to the server renderer's `onError` option, and serves the
same purpose.
When no option is provided, the default behavior is to schedule a
browser task and rethrow the error. This will trigger the normal browser
behavior for errors, including dispatching an error event. If the app
already has error monitoring, this likely will just work as expected
without additional configuration.
However, we can also expose additional metadata about these errors, like
which Suspense boundaries were affected by the de-opt to client
rendering. (I have not exposed any metadata in this commit; API needs
more design work.)
There are other situations besides hydration where we recover from an
error without surfacing it to the user, or notifying an error boundary.
For example, if an error occurs during a concurrent render, it could be
due to a data race, so we try again synchronously in case that fixes it.
We should probably expose a way to log these types of errors, too. (Also
not implemented in this commit.)
* Log all recoverable errors
This expands the scope of onHydrationError to include all errors that
are not surfaced to the UI (an error boundary). In addition to errors
that occur during hydration, this also includes errors that recoverable
by de-opting to synchronous rendering. Typically (or really, by
definition) these errors are the result of a concurrent data race;
blocking the main thread fixes them by prevents subsequent races.
The logic for de-opting to synchronous rendering already existed. The
only thing that has changed is that we now log the errors instead of
silently proceeding.
The logging API has been renamed from onHydrationError
to onRecoverableError.
* Don't log recoverable errors until commit phase
If the render is interrupted and restarts, we don't want to log the
errors multiple times.
This change only affects errors that are recovered by de-opting to
synchronous rendering; we'll have to do something else for errors
during hydration, since they use a different recovery path.
* Only log hydration error if client render succeeds
Similar to previous step.
When an error occurs during hydration, we only want to log it if falling
back to client rendering _succeeds_. If client rendering fails,
the error will get reported to the nearest error boundary, so there's
no need for a duplicate log.
To implement this, I added a list of errors to the hydration context.
If the Suspense boundary successfully completes, they are added to
the main recoverable errors queue (the one I added in the
previous step.)
* Log error with queueMicrotask instead of Scheduler
If onRecoverableError is not provided, we default to rethrowing the
error in a separate task. Originally, I scheduled the task with
idle priority, but @sebmarkbage made the good point that if there are
multiple errors logs, we want to preserve the original order. So I've
switched it to a microtask. The priority can be lowered in userspace
by scheduling an additional task inside onRecoverableError.
* Only use host config method for default behavior
Redefines the contract of the host config's logRecoverableError method
to be a default implementation for onRecoverableError if a user-provided
one is not provided when the root is created.
* Log with reportError instead of rethrowing
In modern browsers, reportError will dispatch an error event, emulating
an uncaught JavaScript error. We can do this instead of rethrowing
recoverable errors in a microtask, which is nice because it avoids any
subtle ordering issues.
In older browsers and test environments, we'll fall back
to console.error.
* Naming nits
queueRecoverableHydrationErrors -> upgradeHydrationErrorsToRecoverable
This deletes some internal behavior that was only used by
useOpaqueIdentifier, as an implementation detail: if an update is
scheduled during the render phase, and something threw an error, we
would try rendering again, either until there were no more errors or
until there were no more render phase updates. This was not a publicly
defined behavior — regular render phase updates are accompanied by
a warning.
Because useOpaqueIdentifier has been replaced by useId, and does not
rely on this implementation detail, we can delete this code.
This gives DevTools a way to detect whether the current React renderer supports Timeline profiling. (Version alone isn't enough to detect this, neither is general profiling support– since these two are controlled by different feature flags.)
* Failing test for Context.Consumer in suspended Suspense
See issue #19701.
* Fix context propagation for offscreen trees
* Address nits
* Specify propagation root for Suspense too
* Pass correct propagation root
* Harden test coverage
This test will fail if we remove propagation, or if we propagate with a root node like fiber.return or fiber.return.return. The additional DEV-only error helps detect a different kind of mistake, like if the thing being passed hasn't actually been encountered on the way up. However, we still leave the actual production loop to check against null so that there is no way we loop forever if the propagation root is wrong.
* Remove superfluous warning
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
Until now, DEV and PROFILING builds of React recorded Timeline profiling data using the User Timing API. This commit changes things so that React records this data by calling methods on the DevTools hook. (For now, DevTools still records that data using the User Timing API, to match previous behavior.)
This commit is large but most of it is just moving things around:
* New methods have been added to the DevTools hook (in "backend/profilingHooks") for recording the Timeline performance events.
* Reconciler's "ReactFiberDevToolsHook" has been updated to call these new methods (when they're present).
* User Timing method calls in "SchedulingProfiler" have been moved to DevTools "backend/profilingHooks" (to match previous behavior, for now).
* The old reconciler tests, "SchedulingProfiler-test" and "SchedulingProfilerLabels-test", have been moved into DevTools "TimelineProfiler-test" to ensure behavior didn't change unexpectedly.
* Two new methods have been added to the injected renderer interface: injectProfilingHooks() and getLaneLabelMap().
Relates to #22529.