This moves the try-catch from around each fiber's mutation phase to
direclty around each user function (effect function, callback, etc).
We already do this when unmounting because if one unmount function
errors, we still need to call all the others so they can clean up
their resources.
Previously we didn't bother to do this for anything but unmount,
because if a mount effect throws, we're going to delete that whole
tree anyway.
But now that we're switching from an iterative loop to a recursive one,
we don't want every call frame on the stack to have a try-catch, since
the error handling requires additional memory.
Wrapping every user function is a bit tedious, but it's better
for performance. Many of them already had try blocks around
them already.
We should always refine the type of fiber before checking the effect
flag, because the fiber tag is more specific.
Now we have a single switch statement for all mutation effects.
The fiber tag is more specific than the effect flag, so we should always
refine the type of work first, to minimize redundant checks.
In the next step I'll move all other other flag checks in this function
into the same switch statement.
There's not really any reason these should be separate functions. The
factoring has gotten sloppy and redundant because there's similar logic
in both places, which is more obvious now that they're combined.
Next I'll start combining the redundant branches.
commitWork is forked into a separate implementation for mutation mode
(DOM) and persistent mode (React Native). But unlike when it was first
introduced, there's more overlap than differences between the forks,
mainly because we've added new types of fibers. So this joins the two
forks and adds more local branches where the behavior actually
diverges: host nodes, host containers, and portals.
I'm about to refactor part of the commit phase to use recursion instead
of iteration. As part of that change, we will no longer assign the
`return` pointer when traversing into a subtree. So I'm disabling
the internal warning that fires if the return pointer is not consistent
with the parent during the commit phase.
I had originally added this warning to help prevent mistakes when
traversing the tree iteratively, but since we're intentionally switching
to recursion instead, we don't need it.
This PR moves the code for transition tracing in the mutation phase that adds transitions to the pending callbacks object (to be called sometime later after paint) from the mutation to the passive phase.
Things to think about:
Passive effects can be flushed before or after paint. How do we make sure that we get the correct end time for the interaction?
* Switched RulesOfHooks.js to use BigInt. Added test and updated .eslintrc.js to use es2020.
* Added BigInt as readonly global in eslintrc.cjs.js and eslintrc.cjs2015.js
* Added comment to RulesOfHooks.js that gets rid of BigInt eslint error
* Got rid of changes in .eslintrc.js and yarn.lock
* Move global down
Co-authored-by: stephen cyron <stephen.cyron@fdmgroup.com>
Co-authored-by: dan <dan.abramov@gmail.com>
This type isn't exported so it's technically not public.
This object mimics a ReadableStream.
Currently this is safe to destructure and call separately but I'm not sure
that's even guaranteed. It should probably be treated as a class in docs.
The Profiler has an advanced feature that shows why a component re-rendered. In the case of props and (class) state, it shows the names of props/state values that changed between renders. For hooks, DevTools tries to detect which ones may been related to the update by comparing prev/next internal hook structures.
My initial implementation tried to detect all changed hooks. In hindsight this is confusing, because only stateful hooks (e.g. useState, useReducer, and useSyncExternalStore) can schedule an update. (Other types of hooks can change between renders, but in a reactive way.) This PR changes the behavior to only report hooks that scheduled the update.
We were suppressing the `react-internals/warning-args` lint rule
for the call to `console.error` in `defaultOnRecoverableError`.
As far as I could tell, the lint rule exists because on dev builds,
we replace all calls to `console.error` with [this error
function](https://github.com/facebook/react/blob/main/packages/shared/consoleWithStackDev.js#L31-L37)
which expects a format string + args and nothing else. We were trying
to pass in an `Error` object directly. After this commit's change,
we will still be passing an `Error` but the transform won't occur.
We used to listen to at the document level for this event. That allowed us to listen to up/down arrow key events while another section
of DevTools (like the search input) was focused. This was a minor UX positive.
(We had to use ownerDocument rather than document for this, because the DevTools extension renders the Components and Profiler tabs into portals.)
This approach caused a problem though: it meant that a react-devtools-inline instance could steal (and prevent/block) keyboard events from other JavaScript on the page– which could even include other react-devtools-inline instances. This is a potential major UX negative.
Given the above trade offs, we now listen on the root of the Tree itself.
* 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
Fixes this issue, where inspecting components in nested renderers results in an error. The reason for this is because we have different fiberToIDMap instances for each renderer, and owners of a component could be in different renderers.
This fix moves the fiberToIDMap and idToArbitraryFiberMap out of the attach method so there's only one instance of each for all renderers.
The internal Container type represents the types of containers that React
can support in its internals that deal with containers.
This didn't include DocumentFragment which we support specifically for
rendering into shadow roots.
However, not all types makes sense to pass into the createRoot API.
One of those is comment nodes that is deprecated and we don't really fully
support. It really only exists for FB legacy.
For createRoot it doesn't make sense to pass a Document since that will try
to empty the document which removes the HTML tag which doesn't work.
Documents can only be passed to hydrateRoot.
Conversely I'm not sure we actually support hydrating a shadow root properly
so I excluded DocumentFragment from hydrateRoot.
Rationale: The only case where the unsupported dialog really matters is React Naive. That's the case where the frontend and backend versions are most likely to mismatch. In React Native, the backend is likely to send the bridge protocol version before sending operations– since the agent does this proactively during initialization.
I've tested the React Native starter app– after forcefully downgrading the backend version to 4.19.1 (see #23307 (comment)) and verified that this change "fixes" things. Not only does DevTools no longer throw an error that causes the UI to be hidden– it works (meaning that the Components tree can be inspected and interacted with).
I noticed while working on a different PR that this test was not
using hydrateRoot correctly. You're meant to pass the initial children
as the second argument.
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] add support for Lazy components in Flight server
Lazy components suspend until resolved just like in Fizz. Add tests to confirm Lazy works with Shared Components and Client Component references.
* Support Lazy elements
React.Lazy can now return an element instead of a Component. This commit implements support for Lazy elements when server rendering.
* add lazy initialization to resolveModelToJson
adding lazying initialization toResolveModelToJson means we use attemptResolveElement's full logic on whatever the resolved type ends up being. This better aligns handling of misued Lazy types like a lazy element being used as a Component or a lazy Component being used as an element.
Before this change, we would sometimes write segments without any content
in them. For example for a Suspense boundary that immediately suspends
we might emit something like:
<div hidden id="123">
<template id="456"></template>
</div>
Where the outer div is just a temporary wrapper and the inner one is a
placeholder for something to be added later.
This serves no purpose.
We should ideally have a heuristic that holds back segments based on byte
size and time. However, this is a straight forward clear win for now.
* 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
* write chunks to a buffer with no re-use
chunks were previously enqueued to a ReadableStream as they were written. We now write them to a view over an ArrayBuffer
and enqueue them only when writing has completed or the buffer's size is exceeded. In addition this copy now ensures
we don't attempt to re-send buffers that have already been transferred.
* refactor writeChunk to be more defensive and efficient
We now defend against overflows using the next views length instead of the current one. this protects us against a future where we use byobRequest and we get longer initial views than we might create after overflowing the first time. Additionally we add in an optimization when we have completely filled up the currentView where we avoid creating subarrays of the chunk to write since it lands exactly on a view boundary. Finally we move the view creation to beginWriting to avoid a runtime check on each write and because we want to reset the view on each beginWriting call in case a throw elsewhere in the program leaves the currentView in an unfinished state
* add tests to exercise codepaths dealing with buffer overlows
* I forgot to call onFatalError
I can't figure out how to write a test for this because it only happens
when there is a bug in React itself which would then be fixed if we found
it.
We're also covered by the protection of ReadableStream which doesn't leak
other errors to us.
* Abort requests if the reader cancels
No need to continue computing at this point.
* Abort requests if node streams get destroyed
This is if the downstream cancels is for example.
* Rename Node APIs for Parity with allReady
The "Complete" terminology is a little misleading because not everything
has been written yet. It's just "Ready" to be written now.
onShellReady
onShellError
onAllReady
* 'close' should be enough
* Move onCompleteAll to .allReady Promise
The onCompleteAll callback can sometimes resolve before the promise that
returns the stream which is tough to coordinate. A more idiomatic API
for a one shot event is a Promise.
That way the way you render for SEO or SSG is:
const stream = await renderToReadableStream(...);
await stream.readyAll;
respondWith(stream);
Ideally this should be a sub-class of ReadableStream but we don't yet
compile these to ES6 and they'd had to be to native class to subclass
a native stream.
I have other ideas for overriding the .tee() method in a subclass anyway.
So this is inline with that strategy.
* Reject the Promise on fatal errors
* Implement addEventListener and removeEventListener on Fabric HostComponent
* add files
* re-add CustomEvent
* fix flow
* Need to get CustomEvent from an import since it won't exist on the global scope by default
* yarn prettier-all
* use a mangled name consistently to refer to imperatively registered event handlers
* yarn prettier-all
* fuzzy null check
* fix capture phase event listener logic
* early exit from getEventListeners more often
* make some optimizations to getEventListeners and the bridge plugin
* fix accumulateInto logic
* fix accumulateInto
* Simplifying getListeners at the expense of perf for the non-hot path
* feedback
* fix impl of getListeners to correctly remove function
* pass all args in to event listeners
This information can help with bug investigation for renderers (like React Native) that embed the DevTools backend into their source (separately from the DevTools frontend, which gets run by the user).
If the DevTools backend is too old to report a version, or if the version reported is the same as the frontend (as will be the case with the browser extension) then only a single version string will be shown, as before. If a different version is reported, then both will be shown separately.
The src property on the dom element will return a fully qualified name and this does not match the dom
src attribute or the props provided to react. instead of reading from the element and re-assigning the property we
assign the property from props which is how it was initially assigned during the render
Co-authored-by: Josh Story <story@hey.com>
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>
We used to check for stream.locked in pull to see if we've been passed to
something that reads yet.
This was a bad hack because it won't actually call pull again if that changes.
The source of this is because the default for "highWaterMark" is 1 on some
streams. So it always wants to add one "chunk" (of size 1).
If we leave our high water mark as 0, we won't fill up any buffers unless we're
asked for more.
This web API is somewhat odd because it would be way more efficient if it
just told us how much the recipient wants instead of calling us once per
chunk.
Anyway, I turns out that if we define ourselves as a "bytes" type of
stream, the default also happens to be a high water mark of 0 so we can
just use that instead.
early load events will be missed by onLoad handlers if they trigger before the tree is committed
to avoid this we reset the src property on the img element to cause the browser to re-load
the img.
Co-authored-by: Josh Story <story@hey.com>
This brings the exports on npm to parity which simplifies things a bit.
We also don't plan to release this. It is used by Draft.js but that caller
will need to switch to flushSync.
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:
* Deprecate renderToNodeStream
* Use renderToPipeableStream in tests instead of renderToNodeStream
This is the equivalent API. This means that we have way less test coverage
of this API but I feel like that's fine since it has a deprecation warning
in it and we have coverage on renderToString that is mostly the same.
* Fix textarea bug
The test changes revealed a bug with textarea. It happens because we
currently always insert trailing comment nodes. We should optimize that
away. However, we also don't really support complex children so we
should toString it anyway which is what partial renderer used to do.
* Update tests that assert number of nodes
These tests are unnecessarily specific about number of nodes.
I special case these, which these tests already do, because they're good
tests to test that the optimization actually works later when we do
fix it.
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
For createRoot, a common mistake is to pass JSX as the second argument,
instead of calling root.render.
For hydrateRoot, a common mistake is to forget to pass children as
the second argument.
The type system will enforce correct usage, but since not everyone uses
types we'll log a helpful warning, too.
* 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.
@sebmarkbage and I audited the feature flags file to review the status
of each feature or experiment. Based on that, I've added some more
comments to the main ReactFeatureFlags module and rearranged them
into groups.
I haven't changed the value of any flags, yet. There are a few we're
going to land but I'll do them as separate PRs.
I removed useMutableSource in a previous PR but forgot this one.
We still export it in the FB builds until we can migrate the internal
callers (Recoil).
This was already defeating the XSS issue that Symbols was meant to protect
against. So you were already supposed to use a polyfill for security.
We rely on real Symbol.for in Flight for Server Components so those require
real symbols anyway.
We also don't really support IE without additional polyfills anyway.
This function was modeled after Node streams where write returns a boolean
whether to keep writing or not. I think we should probably switch this
up and read desired size explicitly in appropriate places.
However, in the meantime, we don't have to return a value where we're
not going to use it. So I split this so that we call writeChunkAndReturn
if we're going to return the boolean.
This should help with the compilation so that they can be inlined.
* tests: add failing test to demonstrate bug in ReadableStream implementation
* Re-add reentrancy avoidance
I removed the equivalency of this in #22446. However, I didn't fully
understand the intended semantics of the spec but I understand this better
now.
The spec is not actually recursive. It won't call pull again inside of a
pull. It might not call it inside startWork neither which the original
issue avoided. However, it will call pull if you enqueue to the controller
without filling up the desired size outside any call.
We could avoid that by returning a Promise from pull that we wait to
resolve until we've performed all our pending tasks. That would be the
more idiomatic solution. That's a bit more involved but since we know
understand it, we can readd the reentrancy hack since we have an easy place
to detect it. If anything, it should probably throw or log here otherwise.
I believe this fixes#22772.
This includes the test from #22889 but should ideally have one for Fizz.
Co-authored-by: Josh Larson <josh.larson@shopify.com>
The unstable-shared-subset.js file is not a public module — it's a
private module that the "react" package maps to when it's accessed from
the "react-server" package.
We originally added it because it was required to make our Rollup
configuration work, because at the time only "public" modules could act
as the entry point for a build artifact — that's why it's prefixed with
"unstable". We've since updated our Rollup config to support private
entry points, so we can remove the extra indirection.
* 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.
This is an old feature that we no longer support. `hydrateRoot` already
throws if you pass a comment node; this change makes `createRoot`
throw, too.
Still enabled in the Facebook build until we migrate the callers.
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.
* Remove deprecated folder mapping
Node v16 deprecated the use of trailing "/" to define subpath folder
mappings in the "exports" field of package.json.
The recommendation is to explicitly list all our exports. We already do
that for all our public modules. I believe the only reason we have a
wildcard pattern is because our package.json files are also used at
build time (by Rollup) to resolve internal source modules that don't
appear in the final npm artifact.
Changing trailing "/" to "/*" fixes the warnings. See
https://nodejs.org/api/packages.html#subpath-patterns for more info.
Since the wildcard pattern only exists so our build script has access to
internal at build time, I've scoped the wildcard to "/src/*". Because
our public modules are located outside the "src" directory, this means
deep imports of our modules will no longer work: only packages that are
listed in the "exports" field.
The only two affected packages are react-dom and react. We need to be
sure that all our public modules are still reachable. I audited the
exports by comparing the entries to the "files" field in package.json,
which represents a complete list of the files that are included in the
final release artifact.
At some point, we should add an e2e packaging test to prevent
regressions; for now, we should have decent coverage because in CI we
run our Jest test suite against the release artifacts.
* Remove umd from exports
Our expectation is that if you're using the UMD builds, you're not
loading them through a normal module system like require or import.
Instead you're probably copying the files directly or loading them from
a CDN like unpkg.
* Use consistent naming for unstable_testing entry point
* Exclude the testing build from non-experimental builds except at FB
* FB builds shouldn't contribute to whether we include the npm files
* Exclude exports fields if we delete the files entry
* Move test to no longer be internal so we can test against the build
* Update the bundle artifact names since they've now changed
* Gate import since it doesn't exist
This indicates that an error has happened before the shell completed and
there's no point in emitting the result of this stream.
This is not quite the same as other fatal errors that can happen even
after streaming as started.
It's also not quite the same as onError before onCompleteShell because
onError can be called for an error inside a Suspense boundary before the
shell completes.
Implement shell error handling in Node SSR fixtures
Instead of hanging indefinitely.
Update Browser Fixture
Expose onErrorShell to the Node build
This API is not Promisified so it's just a separate callback instead.
Promisify the Browser Fizz API
It's now a Promise of a readable stream. The Promise resolves when the
shell completes. If the shell errors, the Promise is rejected.
* 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
This configures the exports field for react-dom.
Notably there are some conditions for the /server entry point where we pick
the export based on environments. Most environments now support Web Streams
which is preferred in those environments.
We already do this using the "browser" field so the "browser" condition
applies here too.
I don't think it's necessary, but I also specified "worker" explicitly
since this is for Service Workers and those are often targeted with
Web Pack's "webworker" target, which is also what Cloudflare currently
recommends.
I also added "deno" but deno is a bit special because this only works if
you run with the node compatibility since otherwise you have to specify
absolute URLs for the imports.
* RawEventEmitter: new event perf profiling mechanism outside of Pressability to capture all touch events, and other event types
* sync
* concise notation
* Move event telemetry event emitter call from Plugin to ReactFabricEventEmitter, to reduce reliance on the plugin system and move the emit call further into the core
* Backout changes to ReactNativeEventPluginOrder
* Properly flow typing event emitter, and emit event to two channels: named and catchall
* fix typing for event name string
* fix typing for event name string
* fix flow
* Add more comments about how the event telemetry system works
* Add more comments about how the event telemetry system works
* rename to RawEventTelemetryEventEmitterOffByDefault
* yarn prettier-all
* rename event
* comments
* improve flow types
* renamed file
* [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.
Refactor DevTools to record Timeline data (in memory) while profiling. Updated the Profiler UI to import/export Timeline data along with legacy profiler data.
Relates to issue #22529
* add failing test for renderToPipeableStream
* Fix context providers in SSR when handling multiple requests. Closes#23089
* Add sibling regression test
Co-authored-by: zhuyi01 <zhuyi01@ke.com>
Co-authored-by: Dan Abramov <dan.abramov@me.com>
Previously we crawled all subtrees, even not-yet-mounted ones, to initialize context values. This was not only unecessary, but it also caused an error to be thrown. This commit adds a test and fixes that behavior.
There was a bug that occurred when a destroy effect is called that causes an update. The update would be added to the updaters list even though the fiber that was calling the destroy effect was unmounted and no longer exists. This PR:
* Adds a patch to Devtools to filter out all in the update list that aren't in the fiberToIDMap (which contains all fibers currently on screen)
Numbers in JavaScript can have precision issues due to how they are encoded. This shows up in snapshot tests sometimes with values like 0.0009999999999999992, which makes the tests hard to read and visually diff.
This PR adds a new snapshot serializers which clamps numbers at 3 decimal points (e.g. the above number 0.0009999999999999992 is serialized as 0.001). This new serializer does not impact non-numeric values, integers, and special numbers like NaN and Infinity.
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.)
* Bypass react event system for custom elements
* Going to try fixing react event system instead
* finally got it to call onChange, but perhaps too many times
* update test
* Removed ReactDOMComponent changes, now works but still doubles for bubbles
* Maybe i should only support bubbling events
* removed some old stuff
* cleaned up changeeventplugin stuff
* prettier, lint
* removed changeeventplugin stuff
* remove unneeded gate for onInput test
* Go back to using ChangeEventPlugin
* Add input+change test
* lint
* Move logic to shouldUseChangeEvent
* use memoizedProps instead of pendingProps
* Run form control behavior before custom element behavior
* add bubbling test
* forgot to append container to body
* add child event target test
* expand <input is=...> test expectations
* Make tests more realistic
* Add extra test
* Add missing gating
* Actually fix gating
Co-authored-by: Dan Abramov <dan.abramov@me.com>
* 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.
This is part of the new custom element features that were implemented
here:
24dd07bd26
When a custom element has a setter for a property and passes the `in`
heuristic, the value passed to the react property should be assigned
directly to the custom element's property, regardless of the type of the
value. However, it was discovered that this isn't working with
functions. This patch makes it work with functions.
Fixes https://github.com/facebook/react/issues/23041
* Add --no-show-signature to "git show" commands.
This fixes errors if the user has configured the following in their ~/.gitconfig:
[log]
showSignature = true
* yarn prettier-all
* DevTools: Only show StrictMode badge on root elements
Showing an inline non-compliance badge for every element in the tree is noisy. This commit changes it so that we only show inline icons for root elements (although we continue to show an icon for inspected elements regardless).
Builds on top of the existing Playwright tests to plug in the test selector API: https://gist.github.com/bvaughn/d3c8b8842faf2ac2439bb11773a19cec
My goals in doing this are to...
1. Experiment with the new API to see what works and what doesn't.
2. Add some test selector attributes (and remove DOM-structure based selectors).
3. Focus the tests on DevTools itself (rather than the test app).
I also took this opportunity to add a few new test cases– like named hooks, editable props, component search, and profiling- just to play around more with the Playwright API.
Relates to issue #22646
This was added when we added error recovery for hydration errors. However,
when the fix up pass happens later on, it'll still call clearContainer in
the commit phase. So this call is unnecessary.
This is being done so that we can embed DevTools within the new React (beta) docs.
The primary changes here are to `react-devtools-inline/backend`:
* Add a new `createBridge` API
* Add an option to the `activate` method to support passing in the custom bridge object.
The `react-devtools-inline` README has been updated to include these new methods.
To verify these changes, this commit also updates the test shell to add a new entry-point for multiple DevTools.
This commit also replaces two direct calls to `window.postMessage()` with `bridge.send()` (and adds the related Flow types).
Adds the concept of subtree modes to DevTools to bridge protocol as follows:
1. Add-root messages get two new attributes: one specifying whether the root is running in strict mode and another specifying whether the root (really the root's renderer) supports the concept of strict mode.
2. A new backend message type (TREE_OPERATION_SET_SUBTREE_MODE). This type specifies a subtree root (id) and a mode (bitmask). For now, the only mode this message deals with is strict mode.
The DevTools frontend has been updated as well to highlight non-StrictMode compliant components.
The changes to the bridge protocol require incrementing the bridge protocol version number, which will also require updating the version of react-devtools-core backend that is shipped with React Native.
* custom element props
* custom element events
* use function type for on*
* tests, htmlFor
* className
* fix ReactDOMComponent-test
* started on adding feature flag
* added feature flag to all feature flag files
* everything passes
* tried to fix getPropertyInfo
* used @gate and __experimental__
* remove flag gating for test which already passes
* fix onClick test
* add __EXPERIMENTAL__ to www flags, rename eventProxy
* Add innerText and textContent to reservedProps
* Emit warning when assigning to read only properties in client
* Revert "Emit warning when assigning to read only properties in client"
This reverts commit 1a093e584c.
* Emit warning when assigning to read only properties during hydration
* yarn prettier-all
* Gate hydration warning test on flag
* Fix gating in hydration warning test
* Fix assignment to boolean properties
* Replace _listeners with random suffix matching
* Improve gating for hydration warning test
* Add outerText and outerHTML to server warning properties
* remove nameLower logic
* fix capture event listener test
* Add coverage for changing custom event listeners
* yarn prettier-all
* yarn lint --fix
* replace getCustomElementEventHandlersFromNode with getFiberCurrentPropsFromNode
* Remove previous value when adding event listener
* flow, lint, prettier
* Add dispatchEvent to make sure nothing crashes
* Add state change to reserved attribute tests
* Add missing feature flag test gate
* Reimplement SSR changes in ReactDOMServerFormatConfig
* Test hydration for objects and functions
* add missing test gate
* remove extraneous comment
* Add attribute->property test
Usually the build script updates transitive React dependencies so that
they refer to the corresponding release version.
For use-sync-external-store, though, we also want to support older
versions of React, too. So the normal behavior of the build script
isn't sufficient.
For now, to unblock, I hardcoded a special case, but we should consider
a better way to handle this in the future.
Adds the concept of "plugins" to the inspected element payload. Also adds the first plugin, one that resolves StyleX atomic style names to their values and displays them as a unified style object (rather than a nested array of objects and booleans).
Source file names are displayed first, in dim color, followed by an ordered set of resolved style values.
For builds with the new feature flag disabled, there is no observable change.
A next step to build on top of this could be to make the style values editable, but change the logic such that editing one directly added an inline style to the item (rather than modifying the stylex class– which may be shared between multiple other components).
Refactor SearchInput component (used in Components tree) to be generic DevTools component with two uses: ComponentSearchInput and TimelineSearchInput.
Refactored Timeline Suspense to more closely match other, newer Suspense patterns (e.g. inspect component, named hooks) and colocated Susepnse code in timelineCache file.
Add search by component name functionality to the Timeline. For now, searching zooms in to the component measure and you can step through each time it rendered using the next/previous arrows.
When an `identifierPrefix` option is given, React will add it to the
beginning of ids generated by `useId`.
The main use case is to avoid conflicts when there are multiple React
roots on a single page.
The server API already supported an `identifierPrefix` option. It's not
only used by `useId`, but also for React-generated ids that are used to
stitch together chunks of HTML, among other things. I added a
corresponding option to the client.
You must pass the same prefix option to both the server and client.
Eventually we may make this automatic by sending the prefix from the
server as part of the HTML stream.
This change adds a new "react-dom/unstable_testing" entry point but I believe its contents will exactly match "react-dom/index" for the stable build. (The experimental build will have the added new selector APIs.)
* move unstable_scheduleHydration to ReactDOMHydrationRoot
* move definition of schedule hydration
* fix test?
* prototype
* fix test
* remove gating because unstable_scheduleHydration is no longer gated through index.stable.js because its exposed through ReactDOMHydrationRoot instead of the ReactDOM package
* remove another gating
Note that this only fixes things for newer versions of React (e.g. 18 alpha). Older versions will remain broken because there's not a good way to read the most recent context value for a location in the tree after render has completed. This is because React maintains a stack of context values during render, but by the time DevTools is called– render has finished and the stack is empty.
* Fix: useId in strict mode
In strict mode, `renderWithHooks` is called twice to flush out
side effects.
Modying the tree context (`pushTreeId` and `pushTreeFork`) is effectful,
so before this fix, the tree context was allocating two slots for a
materialized id instead of one.
To address, I lifted those calls outside of `renderWithHooks`. This
is how I had originally structured it, and it's how Fizz is structured,
too. The other solution would be to reset the stack in between the calls
but that's also a bit weird because we usually only ever reset the
stack during unwind or complete.
* Add test for render phase updates
Noticed this while fixing the previous bug