- method unbinding is no longer supported in Flow for soundness, this added a bunch of suppressions
- Flow now prevents objects to be supertypes of interfaces/classes
ghstack-source-id: d7749cbad8
Pull Request resolved: https://github.com/facebook/react/pull/25412
This upgrade made more expressions invalidate refinements. In some
places this lead to a large number of suppressions that I automatically
suppressed and should be followed up on when the code is touched.
I think most of them might require either manual annotations or moving
a value into a const to allow refinement.
ghstack-source-id: a45b40abf0
Pull Request resolved: https://github.com/facebook/react/pull/25410
This contains one code change, renaming the local function `ChildReconciler` to `createChildReconciler` as it's called as a function, not a constructor and to free up the name for the return value.
* [Fizz/Float] Float for stylesheet resources
This commit implements Float in Fizz and on the Client. The initial set of supported APIs is roughly
1. Convert certain stylesheets into style Resources when opting in with precedence prop
2. Emit preloads for stylesheets and explicit preload tags
3. Dedupe all Resources by href
4. Implement ReactDOM.preload() to allow for imperative preloading
5. Implement ReactDOM.preinit() to allow for imperative preinitialization
Currently supports
1. style Resources (link rel "stylesheet")
2. font Resources (preload as "font")
later updates will include support for scripts and modules
* Expose ref to Offscreen if mode is manual
* Prepend private fields on OffscreenInstance with underscore
* Schedule Ref effect unconditionally on Offscreen
* Make sure Offscreen's ref is detached when unmounted
* Make sure ref is mounted/unmounted in all scenarious
* Nit: pendingProps -> memoizedProps
Co-authored-by: Andrew Clark <git@andrewclark.io>
There's a global queue (`concurrentQueues` in the
ReactFiberConcurrentUpdates module) that is cleared at the beginning of
each render phase.
However, in the case of an eager `setState` bailout where the state is
updated to same value as the current one, we add the update to the queue
without scheduling a render. So the render phase never removes it from
the queue. This can lead to a memory leak if it happens repeatedly
without any other updates.
There's only one place where this ever happens, so the fix was pretty
straightforward.
Currently there's no great way to test this from a Jest test, so I
confirmed locally by checking in an existing test whether the array gets
reset. @sompylasar had an interesting suggestion for how to catch these
in the future: in the development build (perhaps behind a flag), use a
Babel plugin to instrument all module-level variables. Then periodically
sweep to confirm if something has leaked. The logic is that if there's
no React work scheduled, and a module-level variable points to an
object, it very likely indicates a memory leak.
This commit adds a new hook `useEvent` per the RFC [here](https://github.com/reactjs/rfcs/pull/220), gated as experimental.
Co-authored-by: Rick Hanlon <rickhanlonii@gmail.com>
Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>
Co-authored-by: Lauren Tan <poteto@users.noreply.github.com>
add more accurate end time for transitions and update host configs with `requestPostPaintCallback` function and move post paint logic to another module and use it in the work loop
Usually we complete workInProgress before yielding but if that's the
currently suspended one, we don't yet complete it in case we can
immediately unblock it.
If we get interrupted, however, we must unwind it. Where as we usually
assume that we've already completed it.
This shows up when the current work in progress was a Context that pushed
and then it suspends in its immediate children. If we don't unwind,
it won't pop and so we get an imbalance.
* Prevent infinite re-render in StrictMode + Offscreen
* Only fire effects for Offscreen when it is revealed
* Move setting debug fiber into if branch
* Move settings of debug fiber out of if branch
* Internal `act`: Unwrapping resolved promises
This update our internal implementation of `act` to support React's new
behavior for unwrapping promises. Like we did with Scheduler, when
something suspends, it will yield to the main thread so the microtasks
can run, then continue in a new task.
I need to implement the same behavior in the public version of `act`,
but there are some additional considerations so I'll do that in a
separate commit.
* Move throwException to after work loop resumes
throwException is the function that finds the nearest boundary and
schedules it for a second render pass. We should only call it right
before we unwind the stack — not if we receive an immediate ping and
render the fiber again.
This was an oversight in 8ef3a7c that I didn't notice because it happens
to mostly work, anyway. What made me notice the mistake is that
throwException also marks the entire render phase as suspended
(RootDidSuspend or RootDidSuspendWithDelay), which is only supposed to
be happen if we show a fallback. One consequence was that, in the
RootDidSuspendWithDelay case, the entire commit phase was blocked,
because that's the exit status we use to block a bad fallback
from appearing.
* Use expando to check whether promise has resolved
Add a `status` expando to a thrown thenable to track when its value has
resolved.
In a later step, we'll also use `value` and `reason` expandos to track
the resolved value.
This is not part of the official JavaScript spec — think of
it as an extension of the Promise API, or a custom interface that is a
superset of Thenable. However, it's inspired by the terminology used
by `Promise.allSettled`.
The intent is that this will be a public API — Suspense implementations
can set these expandos to allow React to unwrap the value synchronously
without waiting a microtask.
* Scaffolding for `experimental_use` hook
Sets up a new experimental hook behind a feature flag, but does not
implement it yet.
* use(promise)
Adds experimental support to Fiber for unwrapping the value of a promise
inside a component. It is not yet implemented for Server Components,
but that is planned.
If promise has already resolved, the value can be unwrapped
"immediately" without showing a fallback. The trick we use to implement
this is to yield to the main thread (literally suspending the work
loop), wait for the microtask queue to drain, then check if the promise
resolved in the meantime. If so, we can resume the last attempted fiber
without unwinding the stack. This functionality was implemented in
previous commits.
Another feature is that the promises do not need to be cached between
attempts. Because we assume idempotent execution of components, React
will track the promises that were used during the previous attempt and
reuse the result. You shouldn't rely on this property, but during
initial render it mostly just works. Updates are trickier, though,
because if you used an uncached promise, we have no way of knowing
whether the underlying data has changed, so we have to unwrap the
promise every time. It will still work, but it's inefficient and can
lead to unnecessary fallbacks if it happens during a discrete update.
When we implement this for Server Components, this will be less of an
issue because there are no updates in that environment. However, it's
still better for performance to cache data requests, so the same
principles largely apply.
The intention is that this will eventually be the only supported way to
suspend on arbitrary promises. Throwing a promise directly will
be deprecated.
This PR adds the `onMarkerIncomplete` callback for tracing marker name changes. Specifically, this PR:
* Adds the `onMarkerIncomplete` callback
* When a tracing marker is deleted, call `onMarkerIncomplete` with the `name` of the tracing marker for the tracing marker.
* When a tracing marker/suspense boundary is deleted, call `onMarkerIncomplete` for every parent tracing marker with the `name` of the tracing marker that caused the transition to be incomplete.
* Don't call `onTransitionComplete` or `onMarkerComplete` when `onMarkerIncomplete` is called for all tracing markers with the same transitions, but continue to call `onTransitionProgress`
* Skip double invoking effects in Offscreen
* Run yarn replace-fork
* Use executionContext to disable profiler timer
* Restructure recursion into two functions
* Fix ReactStrictMode test
* Use gate pragma in ReacetOffscreenStrictMode test
* Set and reset current debug fiber in dev
* Skip over paths that don't include any insertions
* Extract common logic to check for profiling to a helper function
* Remove hasPassiveEffects flag from StrictMode
* Fix flow issues
* Revert "Skip over paths that don't include any insertions"
* Yield to main thread if continuation is returned
Instead of using an imperative method `requestYield` to ask Scheduler to
yield to the main thread, we can assume that any time a Scheduler task
returns a continuation callback, it's because it wants to yield to the
main thread. We can assume the task already checked some condition that
caused it to return a continuation, so we don't need to do any
additional checks — we can immediately yield and schedule a new task
for the continuation.
The replaces the `requestYield` API that I added in ca990e9.
* Move unwind after error into main work loop
I need to be able to yield to the main thread in between when an error
is thrown and when the stack is unwound. (This is the motivation behind
the refactor, but it isn't implemented in this commit.) Currently the
unwind is inlined directly into `handleError`.
Instead, I've moved the unwind logic into the main work loop. At the
very beginning of the function, we check to see if the work-in-progress
is in a "suspended" state — that is, whether it needs to be unwound. If
it is, we will enter the unwind phase instead of the begin phase.
We only need to perform this check when we first enter the work loop:
at the beginning of a Scheduler chunk, or after something throws. We
don't need to perform it after every unit of work.
* Yield to main thread whenever a fiber suspends
When a fiber suspends, we should yield to the main thread in case the
data is already cached, to unblock a potential ping event.
By itself, this commit isn't useful because we don't do anything special
in the case where to do receive an immediate ping event. I've split this
out only to demonstrate that it doesn't break any existing behavior.
See the next commit for full context and motivation.
* Resume immediately pinged fiber without unwinding
If a fiber suspends, and is pinged immediately in a microtask (or a
regular task that fires before React resumes rendering), try rendering
the same fiber again without unwinding the stack. This can be super
helpful when working with promises and async-await, because even if the
outermost promise hasn't been cached before, the underlying data may
have been preloaded. In many cases, we can continue rendering
immediately without having to show a fallback.
This optimization should work during any concurrent (time-sliced)
render. It doesn't work during discrete updates because those are
semantically required to finish synchronously — those get the current
behavior.
This PR cleans up some of the transition tracing code by:
* Looping through marker transitions only when we process the markerComplete callback (rather than in the commit phase) so we block for less time during commit.
* Renaming `PendingSuspenseBoundaries` to `pendingBoundaries`
* Cleaning up the callback functions
This PR adds support for `onMarkerProgress` (`onTransitionProgress(transitionName: string, markerName: string, startTime: number, currentTime: number, pending: Array<{name: null | string}>)`)
We call this callback when:
* When **a child suspense boundary of the marker commits in a fallback state**. Only the suspense boundaries that are triggered and commit in a fallback state when the transition first occurs (and all subsequent suspense boundaries in the initial suspense boundary's subtree) are considered a part of the transition
* **A child suspense boundary of the marker resolves**
When we call `onMarkerProgress`, we call the function with a `pending` array. This array contains the names of the transition's suspense boundaries that are still in a fallback state
This PR checks to see if `transition.name` is defined before adding the transition so we avoid doing unnecessary work for transitions without a transition name
This PR adds support for `onTransitionProgress` (`onTransitionProgress(transitionName: string, startTime: number, currentTime: number, pending: Array<{name: null | string}>)`)
We call this callback when:
* When **a child suspense boundary of the transition commits in a fallback state**. Only the suspense boundaries that are triggered and commit in a fallback state when the transition first occurs (and all subsequent suspense boundaries in the initial suspense boundary's subtree) are considered a part of the transition
* **A child suspense boundary of the transition resolves**
When we call `onTransitionProgress`, we call the function with a `pending` array. This array contains the names of the transition's suspense boundaries that are still in a fallback state
This applies forked changes from the "new" reconciler to the "old" one.
Includes:
- 67de5e3 [FORKED] Hidden trees should capture Suspense
- 6ab05ee [FORKED] Track nearest Suspense handler on stack
- 051ac55 [FORKED] Add HiddenContext to track if subtree is hidden
This PR changes the type of the object we store in the pending transitions callbacks map. Previously, we were recreating the transition object that we initially created during `startTransition`. However, we can actually reuse the object instead (and it also gives us a stable way to identify a transition). This PR changes the implementation to reuse the transition object instead of creating a new one
This applies forked changes from the "new" reconciler to the "old" one.
Includes:
- d410f0a [FORKED] Bugfix: Offscreen instance is null during setState
- 58bb117 [FORKED] Check for infinite update loops even if unmounted
- 31882b5 [FORKED] Bugfix: Revealing a hidden update
- 17691ac [FORKED] Don't update childLanes until after current render
This PR adds code to add a marker complete callback to the queue. It also adds code to process marker complete callback.
Marker complete callbacks, in addition to the fields that transition complete callbacks need, also have a `markerName` field
* [FORKED] Check for infinite update loops even if unmounted
The infinite update loop check doesn't need to run if the component
already unmounted, because an update to an unmounted component can't
cause a re-render. But because we used to run the check in this case,
anyway, I found one test in www that happens to "rely on" this behavior
(accidentally). The test is a pretty messy snapshot thing that I have no
interest fixing so to unblock the sync I'm just going to switch this
back to how it was.
* Add previous commit to forked revisions
* extend onRecoverableError API to support errorInfo
errorInfo has been used in Error Boundaries wiht componentDidCatch for a while now. To date this metadata only contained a componentStack. onRecoverableError only receives an error (type mixed) argument and thus providing additional error metadata was not possible without mutating user created mixed objects.
This change modifies rootConcurrentErrors rootRecoverableErrors, and hydrationErrors so all expect CapturedValue types. additionally a new factory function allows the creation of CapturedValues from a value plus a hash and stack.
In general, client derived CapturedValues will be created using the original function which derives a componentStack from a fiber and server originated CapturedValues will be created using with a passed in hash and optional componentStack.
* Always push updates to interleaved queue first
Interleaves updates (updates that are scheduled while another render
is already is progress) go into a special queue that isn't applied until
the end of the current render. They are transferred to the "real" queue
at the beginning of the next render.
Currently we check during `setState` whether an update should go
directly onto the real queue or onto the special interleaved queue. The
logic is subtle and it can lead to bugs if you mess it up, as in #24400.
Instead, this changes it to always go onto the interleaved queue. The
behavior is the same but the logic is simpler.
As a further step, we can also wait to update the `childLanes` until
the end of the current render. I'll do this in the next step.
* Move setState return path traversal to own call
A lot of the logic around scheduling an update needs access to the
fiber root. To obtain this reference, we must walk up the fiber return
path. We also do this to update `childLanes` on all the parent
nodes, so we can use the same traversal for both purposes.
The traversal currently happens inside `scheduleUpdateOnFiber`, but
sometimes we need to access it beyond that function, too.
So I've hoisted the traversal out of `scheduleUpdateOnFiber` into its
own function call that happens at the beginning of the
`setState` algorithm.
* Rename ReactInterleavedUpdates -> ReactFiberConcurrentUpdates
The scope of this module is expanding so I've renamed accordingly. No
behavioral changes.
* Enqueue and update childLanes in same function
During a setState, the childLanes are updated immediately, even if a
render is already in progress. This can lead to subtle concurrency bugs,
so the plan is to wait until the in-progress render has finished before
updating the childLanes, to prevent subtle concurrency bugs.
As a step toward that change, when scheduling an update, we should not
update the childLanes directly, but instead defer to the
ReactConcurrentUpdates module to do it at the appropriate time.
This makes markUpdateLaneFromFiberToRoot a private function that is
only called from the ReactConcurrentUpdates module.
* [FORKED] Don't update childLanes until after current render
(This is the riskiest commit in the stack. Only affects the "new"
reconciler fork.)
Updates that occur in a concurrent event while a render is already in
progress can't be processed during that render. This is tricky to get
right. Previously we solved this by adding concurrent updates to a
special `interleaved` queue, then transferring the `interleaved` queue
to the `pending` queue after the render phase had completed.
However, we would still mutate the `childLanes` along the parent path
immediately, which can lead to its own subtle data races.
Instead, we can queue the entire operation until after the render phase
has completed. This replaces the need for an `interleaved` field on
every fiber/hook queue.
The main motivation for this change, aside from simplifying the logic a
bit, is so we can read information about the current fiber while we're
walking up its return path, like whether it's inside a hidden tree.
(I haven't done anything like that in this commit, though.)
* Add 17691ac to forked revisions
When hydrating a suspense boundary an error or a suspending fiber can often lead to a cascade of hydration errors. While in many cases these errors are simply discarded (e.g. when teh root does not commit and we fall back to client rendering) the use of invokeGuardedCallback can lead to many of these errors appearing as uncaught in the browser console. This change avoids error replaying using invokeGuardedCallback when we are hydrating a suspense boundary and have either already suspended or we have one previous error which was replayed.
I changed the type of this functions returned value but forgot to change
the check.
It happens to work before anyway, because eventually the interleaved
updates will get transferred at the beginning of the next render phase.
But this is more logically consistent.
* Regression test: Interleaved update race condition
Demonstrates the bug reported in #24350.
* Bugfix: Last update wins, even if interleaved
"Interleaved" updates are updates that are scheduled while a render is
already in progress. We put these on a special queue so that they don't
get processed during the current render. Then we transfer them to
the "real" queue after the render has finished.
There was a race condition where an update is received after the render
has finished but before the interleaved update queue had been
transferred, causing the updates to be queued in the wrong order.
The fix I chose is to check if the interleaved updates queue is empty
before adding any update to the real queue. If it's not empty, then
the new update must also be treated as interleaved.
Add pendingPassiveTransitions work loop module level variable. Because workInProgressTransitions might change before we process it in the passive effects, we introduce a new variable, pendingPassiveTransitions, where we store the transitions until we can actually process them in the commit phase.
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?
* 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