* Add `supportsMicrotasks` to the host config
Only certain renderers support scheduling a microtask, so we need a
renderer specific flag that we can toggle. That way it's off for some
renderers and on for others.
I copied the approach we use for the other optional parts of the host
config, like persistent mode and test selectors.
Why isn't the feature flag sufficient?
The feature flag modules, confusingly, are not renderer-specific, at
least when running the our tests against the source files. They are
meant to correspond to a release channel, not a renderer, but we got
confused at some point and haven't cleaned it up.
For example, when we run `yarn test`, Jest loads the flags from the
default `ReactFeatureFlags.js` module, even when we import the React
Native renderer — but in the actual builds, we load a different feature
flag module, `ReactFeatureFlags.native-oss.js.` There's no way in our
current Jest load a different host config for each renderer, because
they all just import the same module. We should solve this by creating
separate Jest project for each renderer, so that the flags loaded when
running against source are the same ones that we use in the
compiled bundles.
The feature flag (`enableDiscreteMicrotasks`) still exists — it's used
to set the React DOM host config's `supportsMicrotasks` flag to `true`.
(Same for React Noop) The important part is that turning on the feature
flag does *not* affect the other renderers, like React Native.
The host config will likely outlive the feature flag, too, since the
feature flag only exists so we can gradually roll it out and measure the
impact in production; once we do, we'll remove it. Whereas the host
config flag may continue to be used to disable the discrete microtask
behavior for RN, because RN will likely use a native (non-JavaScript)
API to schedule its tasks.
* Add `supportsMicrotask` to react-reconciler README
* Concurrent Mode test for uMS render mutation
Same test as the one added in #20665, but for Concurrent Mode.
* Use double render to detect render phase mutation
PR #20665 added a mechanism to detect when a `useMutableSource` source
is mutated during the render phase. It relies on the fact that we double
invoke components that error during development using
`invokeGuardedCallback`. If the version in the double render doesn't
match the first, that indicates there must have been a mutation during
render.
At first I thought it worked by detecting inside the *other* double
render, the one we do for Strict Mode. It turns out that while it does
warn then, the warning is suppressed, because we suppress all console
methods that occur during the Strict Mode double render. So it's really
the `invokeGuardedCallback` one that makes it work.
Anyway, let's set that aside that issue for a second. I realized during
review that errors that occur during the Strict Mode double render
reveal a useful property: A pure component will never throw during the
double render, because if it were pure, it would have also thrown during
the first render... in which case it wouldn't have double rendered! This
is true of all such errors, not just the one thrown by
`useMutableSource`.
Given this, we can simplify the `useMutableSource` mutation detection
mechanism. Instead of tracking and comparing the source's version, we
can instead check if we're inside a double render when the error is
thrown.
To get around the console suppression issue, I changed the warning to an
error. It errors regardless, in both dev and prod, so it doesn't have
semantic implications.
However, because of the paradox I described above, we arguably
_shouldn't_ throw an error in development, since we know that error
won't happen in production, because prod doesn't double render. (It's
still a tearing bug, but that doesn't mean the component will actually
throw.) I considered that, but that requires a larger conversation about
how to handle errors that we know are only possible in development. I
think we should probably be suppressing *all* errors (with a warning)
that occur during a double render.
Changed previous error message from:
> Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.
To:
> Cannot read from mutable source during the current render without tearing. This may be a bug in React. Please file an issue.
Also added a DEV only warning about the unsafe side effect:
> A mutable source was mutated while the %s component was rendering. This is not supported. Move any mutations into event handlers or effects.
I think this is the best we can do without adding production overhead that we'd probably prefer to avoid.
* Remove react/unstable_cache
We're probably going to make it available via the dispatcher. Let's remove this for now.
* Add readContext() to the dispatcher
On the server, it will be per-request.
On the client, there will be some way to shadow it.
For now, I provide it on the server, and throw on the client.
* Use readContext() from react-fetch
This makes it work on the server (but not on the client until we implement it there.)
Updated the test to use Server Components. Now it passes.
* Fixture: Add fetch from a Server Component
* readCache -> getCacheForType<T>
* Add React.unstable_getCacheForType
* Add a feature flag
* Fix Flow
* Add react-suspense-test-utils and port tests
* Remove extra Map lookup
* Unroll async/await because build system
* Add some error coverage and retry
* Add unstable_getCacheForType to Flight entry
* Encode Symbols as special rows that can be referenced by models
If a symbol was extracted from Symbol.for(...) then we can reliably
recreate the same symbol on the client.
S123:"react.suspense"
M456:{mySymbol: '$123'}
This doesn't suffer from the XSS problem because you have to write actual
code to create one of these symbols. That problem is only a problem because
values pass through common other usages of JSON which are not secure.
Since React encodes its built-ins as symbols, we can now use them as long
as its props are serializable. Like Suspense.
* Refactor resolution to avoid memo hack
Going through createElement isn't quite equivalent for ref and key in props.
* Reuse symbol ids that have already been written earlier in the stream
* Remove Blocks
* Remove Flight Server Runtime
There's no need for this now that the JSResource is part of the bundler
protocol. Might need something for Webpack plugin specifically later.
* Devtools
* Improve error message by expanding the object in question
* Don't warn for key/ref getters
* Error if refs are passed in server components or to client components
* Remove distinction between long, short transitions
We're removing the `timeoutMs` option, so there's no longer any
distinction between "short" and "long" transitions. They're all treated
the same.
This commit doesn't remove `timeoutMs` yet, only combines the internal
priority levels.
* Disable `timeoutMs` argument
tl;dr
-----
- We're removing the `timeoutMs` argument from `useTransition`.
- Transitions will either immediately switch to a skeleton/placeholder
view (when loading new content) or wait indefinitely until the data
resolves (when refreshing stale content).
- This commit disables the `timeoutMS` so that the API has the desired
semantics. It doesn't yet update the types or migrate all the test
callers. I'll do those steps in follow-up PRs.
Motivation
----------
Currently, transitions initiated by `startTransition` / `useTransition`
accept a `timeoutMs` option. You can use this to control the maximum
amount of time that a transition is allowed to delay before we give up
and show a placeholder.
What we've discovered is that, in practice, every transition falls into
one of two categories: a **load** or a **refresh**:
- **Loading a new screen**: show the next screen as soon as possible,
even if the data hasn't finished loading. Use a skeleton/placeholder
UI to show progress.
- **Refreshing a screen that's already visible**: keep showing the
current screen indefinitely, for as long as it takes to load the fresh
data, even if the current data is stale. Use a pending state (and
maybe a busy indicator) to show progress.
In other words, transitions should either *delay indefinitely* (for a
refresh) or they should show a placeholder *instantly* (for a load).
There's not much use for transitions that are delayed for a
small-but-noticeable amount of time.
So, the plan is to remove the `timeoutMs` option. Instead, we'll assign
an effective timeout of `0` for loads, and `Infinity` for refreshes.
The mechanism for distinguishing a load from a refresh already exists in
the current model. If a component suspends, and the nearest Suspense
boundary hasn't already mounted, we treat that as a load, because
there's nothing on the screen. However, if the nearest boundary is
mounted, we treat that as a refresh, since it's already showing content.
If you need to fix a transition to be treated as a load instead of a
refresh, or vice versa, the solution will involve rearranging the
location of your Suspense boundaries. It may also involve adding a key.
We're still working on proper documentation for these patterns. In the
meantime, please reach out to us if you run into problems that you're
unsure how to fix.
We will remove `timeoutMs` from `useDeferredValue`, too, and apply the
same load versus refresh semantics to the update that spawns the
deferred value.
Note that there are other types of delays that are not related to
transitions; for example, we will still throttle the appearance of
nested placeholders (we refer to this as the placeholder "train model"),
and we may still apply a Just Noticeable Difference heuristic (JND) in
some cases. These aren't going anywhere. (Well, the JND heuristic might
but for different reasons than those discussed above.)
* Failing test for #19608
* Attach Listeners Eagerly to Roots and Portal Containers
* Forbid createEventHandle with custom events
We can't support this without adding more complexity. It's not clear that this is even desirable, as none of our existing use cases need custom events. This API primarily exists as a deprecation strategy for Flare, so I don't think it is important to expand its support beyond what Flare replacement code currently needs. We can later revisit it with a better understanding of the eager/lazy tradeoff but for now let's remove the inconsistency.
* Reduce risk by changing condition only under the flag
Co-authored-by: koba04 <koba0004@gmail.com>
Adds several new experimental APIs to aid with automated testing.
Each of the methods below accepts an array of "selectors" that identifies a path (or paths) through a React tree. There are four basic selector types:
* Component: Matches Fibers with the specified React component type
* Role: Matches Host Instances matching the (explicit or implicit) accessibility role.
* Test name: Matches Host Instances with a data-testname attribute.
* Text: Matches Host Instances that directly contain the specified text.
* There is also a special lookahead selector type that enables further matching within a path (without actually including the path in the result). This selector type was inspired by the :has() CSS pseudo-class. It enables e.g. matching a <section> that contained a specific header text, then finding a like button within that <section>.
API
* findAllNodes(): Finds all Host Instances (e.g. HTMLElement) within a host subtree that match the specified selector criteria.
* getFindAllNodesFailureDescription(): Returns an error string describing the matched and unmatched portions of the selector query.
* findBoundingRects(): For all React components within a host subtree that match the specified selector criteria, return a set of bounding boxes that covers the bounds of the nearest (shallowed) Host Instances within those trees.
* observeVisibleRects(): For all React components within a host subtree that match the specified selector criteria, observe if it’s bounding rect is visible in the viewport and is not occluded.
* focusWithin(): For all React components within a host subtree that match the specified selector criteria, set focus within the first focusable Host Instance (as if you started before this component in the tree and moved focus forwards one step).
See PR #18796 for more information.
All of the changes I've made in this commit are behind the
`enableNewReconciler` flag. Merging this to master will not affect the
open source builds or the build that we ship to Facebook.
The only build that is affected is the `ReactDOMForked` build, which is
deployed to Facebook **behind an experimental flag (currently disabled
for all users)**. We will use this flag to gradually roll out the new
reconciler, and quickly roll it back if we find any problems.
Because we have those protections in place, what I'm aiming for with
this initial PR is the **smallest possible atomic change that lands
cleanly and doesn't rely on too many hacks**. The goal has not been to
get every single test or feature passing, and it definitely is not to
implement all the features that we intend to build on top of the new
model. When possible, I have chosen to preserve existing semantics and
defer changes to follow-up steps. (Listed in the section below.)
(I did not end up having to disable any tests, although if I had, that
should not have necessarily been a merge blocker.)
For example, even though one of the primary goals of this project is to
improve our model for parallel Suspense transitions, in this initial
implementation, I have chosen to keep the same core heuristics for
sequencing and flushing that existed in the ExpirationTimes model: low
priority updates cannot finish without also finishing high priority
ones.
Despite all these precautions, **because the scope of this refactor is
inherently large, I do expect we will find regressions.** The flip side
is that I also expect the new model to improve the stability of the
codebase and make it easier to fix bugs when they arise.
* Rename ReactCache -> ReactCacheOld
We still use it in some tests so I'm going to leave it for now. I'll start making the new one in parallel in the react package.
* Add react/unstable-cache entry point
* Add react-data entry point
* Initial implementation of cache and data/fetch
* Address review
* Detect double stacks in the new format in tests
* Remove unnecessary uses of getStackByFiberInDevAndProd
These all execute in the right execution context already.
* Set the debug fiber around the cases that don't have an execution context
* Remove stack detection in our console log overrides
We never pass custom stacks as part of the args anymore.
* Bonus: Don't append getStackAddendum to invariants
We print component stacks for every error anyway so this is just duplicate
information.
* Add useOpaqueIdentifier Hook
We currently use unique IDs in a lot of places. Examples are:
* `<label for="ID">`
* `aria-labelledby`
This can cause some issues:
1. If we server side render and then hydrate, this could cause an
hydration ID mismatch
2. If we server side render one part of the page and client side
render another part of the page, the ID for one part could be
different than the ID for another part even though they are
supposed to be the same
3. If we conditionally render something with an ID , this might also
cause an ID mismatch because the ID will be different on other
parts of the page
This PR creates a new hook `useUniqueId` that generates a different
unique ID based on whether the hook was called on the server or client.
If the hook is called during hydration, it generates an opaque object
that will rerender the hook so that the IDs match.
Co-authored-by: Andrew Clark <git@andrewclark.io>
* Resolve Server-side Blocks instead of Components
React elements should no longer be used to extract arbitrary data but only
for prerendering trees.
Blocks are used to create asynchronous behavior.
* Resolve Blocks in the Client
* Tests
* Bug fix relay JSON traversal
It's supposed to pass the original object and not the new one.
* Lint
* Move Noop Module Test Helpers to top level entry points
This module has shared state. It needs to be external from builds.
This lets us test the built versions of the Noop renderer.
useMutableSource hook
useMutableSource() enables React components to safely and efficiently read from a mutable external source in Concurrent Mode. The API will detect mutations that occur during a render to avoid tearing and it will automatically schedule updates when the source is mutated.
RFC: reactjs/rfcs#147
* Update Flow to 0.84
* Fix violations
* Use inexact object syntax in files from fbsource
* Fix warning extraction to use a modern parser
* Codemod inexact objects to new syntax
* Tighten types that can be exact
* Revert unintentional formatting changes from codemod
* Don't bother including `unstable_` in error
The method names don't get stripped out of the production bundles
because they are passed as arguments to the error decoder.
Let's just always use the unprefixed APIs in the messages.
* Set up experimental builds
The experimental builds are packaged exactly like builds in the stable
release channel: same file structure, entry points, and npm package
names. The goal is to match what will eventually be released in stable
as closely as possible, but with additional features turned on.
Versioning and Releasing
------------------------
The experimental builds will be published to the same registry and
package names as the stable ones. However, they will be versioned using
a separate scheme. Instead of semver versions, experimental releases
will receive arbitrary version strings based on their content hashes.
The motivation is to thwart attempts to use a version range to match
against future experimental releases. The only way to install or depend
on an experimental release is to refer to the specific version number.
Building
--------
I did not use the existing feature flag infra to configure the
experimental builds. The reason is because feature flags are designed
to configure a single package. They're not designed to generate multiple
forks of the same package; for each set of feature flags, you must
create a separate package configuration.
Instead, I've added a new build dimension called the **release
channel**. By default, builds use the **stable** channel. There's
also an **experimental** release channel. We have the option to add more
in the future.
There are now two dimensions per artifact: build type (production,
development, or profiling), and release channel (stable or
experimental). These are separate dimensions because they are
combinatorial: there are stable and experimental production builds,
stable and experimental developmenet builds, and so on.
You can add something to an experimental build by gating on
`__EXPERIMENTAL__`, similar to how we use `__DEV__`. Anything inside
these branches will be excluded from the stable builds.
This gives us a low effort way to add experimental behavior in any
package without setting up feature flags or configuring a new package.
* Remove "Invariant Violation" from dev errors
When I made the change to compile `invariant` to throw expressions, I
left a small runtime to set the error's `name` property to "Invariant
Violation" to maintain the existing behavior.
I think we can remove it. The argument for keeping it is to preserve
continuity in error logs, but this only affects development errors,
anyway: production error messages are replaced with error codes.
* Pass prod error messages directly to constructor
Updates the `invariant` transform to pass an error message string
directly to the Error constructor, instead of mutating the
message property.
Turns this code:
```js
invariant(condition, 'A %s message that contains %s', adj, noun);
```
into this:
```js
if (!condition) {
throw Error(
__DEV__
? `A ${adj} message that contains ${noun}`
: formatProdErrorMessage(ERR_CODE, adj, noun)
);
}
```
The error transform works by replacing calls to `invariant` with
an `if` statement.
Since we're replacing a call expression with a statement, Babel wraps
the new statement in an immediately-invoked function expression (IIFE).
This wrapper is unnecessary in practice because our `invariant` calls
are always part of their own expression statement.
In the production bundle, the function wrappers are removed by Closure.
But they remain in the development bundles.
This commit updates the transform to confirm that an `invariant` call
expression's parent node is an expression statement. (If not, it throws
a transform error.)
Then, it replaces the expression statement instead of the expression
itself, effectively removing the extraneous IIFE wrapper.
* Outline push/pop logic in `renderRoot`
I want to get rid of the the `isSync` argument to `renderRoot`, and
instead use separate functions for concurrent and synchronous render.
As a first step, this extracts the push/pop logic that happens before
and after the render phase into helper functions.
* Extract `catch` block into helper function
Similar to previous commit. Extract error handling logic into
a separate function so it can be reused.
* Fork `renderRoot` for sync and concurrent
Removes `isSync` argument in favor of separate functions.
* Extra "root completion" logic to separate function
Moving this out to avoid an accidental early return, which would
bypass the call to `ensureRootIsScheduled` and freeze the UI.
* Inline `renderRoot`
Inlines `renderRoot` into `performConcurrentWorkOnRoot` and
`performSyncWorkOnRoot`. This lets me remove the `isSync` argument
and also get rid of a redundant try-catch wrapper.
* [suspense][error handling] Add failing unit test
Covers an edge case where an error is thrown inside the complete phase
of a component that is in the return path of a component that suspends.
The second error should also be handled (i.e. able to be captured by
an error boundary.
The test is currently failing because there's a call to
`completeUnitOfWork` inside the main render phase `catch` block. That
call is not itself wrapped in try-catch, so anything that throws is
treated as a fatal/unhandled error.
I believe this bug is only observable if something in the host config
throws; and, only in legacy mode, because in concurrent/batched mode,
`completeUnitOfWork` on fiber that throws follows the "unwind" path
only, not the "complete" path, and the "unwind" path does not call
any host config methods.
* [scheduler][profiler] Start time of delayed tasks
Fixes a bug in the Scheduler profiler where the start time of a delayed
tasks is always 0.
* Remove ad hoc `throw`
Fatal errors (errors that are not captured by an error boundary) are
currently rethrown from directly inside the render phase's `catch`
block. This is a refactor hazard because the code in this branch has
to mirror the code that happens at the end of the function, when exiting
the render phase in the normal case.
This commit moves the throw to the end, using a new root exit status.
* Handle errors that occur on unwind
* Don't invoke listeners on parent of dehydrated event target
* Move Suspense boundary check to getClosestInstanceFromNode
Now getClosestInstanceFromNode can return either a host component,
host text component or suspense component when the suspense
component is dehydrated.
We then use that to ignore events on a suspense component.
* Attach the HostRoot fiber to the DOM container
This lets us detect if an event happens on this root's subtree before it
has rendered something.
* Add todo
The approach of checking isFiberMounted answers if we might be in an
in-progress hydration but it doesn't answer which root or boundary
might be in-progress so we don't know what to wait for.
This needs some refactoring.
* Refactor isFiberMountedImpl to getNearestMountedFiber
We'll need the nearest boundary for event replaying so this prepares for
that.
This surfaced an issue that we attach Hydrating tag on the root but normally
this (and Placement) is attached on the child. This surfaced an issue
that this can lead to both Placement and Hydrating effects which is not
supported so we need to ensure that we only ever use one or the other.
* Add todo for bug I spotted
* Cache tags
* Check the ContainerInstanceKey before the InstanceKey
The container is inside the instance, so we must find it before the
instance, since otherwise we'll miss it.
* Move dehydrated to be child of regular SuspenseComponent
We now store the comment node on SuspenseState instead and that indicates
that this SuspenseComponent is still dehydrated.
We also store a child but that is only used to represent the DOM node for
deletions and getNextHostSibling.
* Move logic from DehydratedSuspenseComponent to SuspenseComponent
Forked based on SuspenseState.dehydrated instead.
* Retry logic for dehydrated boundary
We can now simplify the logic for retrying dehydrated boundaries without
hydrating. This is becomes simply a reconciliation against the dehydrated
fragment which gets deleted, and the new children gets inserted.
* Remove dehydrated from throw
Instead we use the regular Suspense path. To save code, we attach retry
listeners in the commit phase even though technically we don't have to.
* Pop to nearest Suspense
I think this is right...?
* Popping hydration state should skip past the dehydrated instance
* Split mount from update and special case suspended second pass
The DidCapture flag isn't used consistently in the same way. We need
further refactor for this.
* Reorganize update path
If we remove the dehydration status in the first pass and then do a second
pass because we suspended, then we need to continue as if it didn't
previously suspend. Since there is no fragment child etc.
However, we must readd the deletion.
* Schedule context work on the boundary and not the child
* Warn for Suspense hydration in legacy mode
It does a two pass render that client renders the content.
* Rename DehydratedSuspenseComponent -> DehydratedFragment
This now doesn't represent a suspense boundary itself. Its parent does.
This Fiber represents the fragment around the dehydrated content.
* Refactor returns
Avoids the temporary mutable variables. I kept losing track of them.
* Add a comment explaining the type.
Placing it in the type since that's the central point as opposed to spread
out.
Upgraded from Babel 6 to Babel 7.
The only significant change seems to be the way `@babel/plugin-transform-classes` handles classes differently from `babel-plugin-transform-es2015-classes`. In regular mode, the former injects a `_createClass` function that increases the bundle size, and in the latter it removes the safeguard checks. However, this is okay because we don't all classes in new features, and we want to deprecate class usage in the future in the react repo.
Co-authored-by: Luna Ruan <luna@fb.com>
Co-authored-by: Abdul Rauf <abdulraufmujahid@gmail.com>
Co-authored-by: Maksim Markelov <maks-markel@mail.ru>