Background:
State updates that are scheduled in a layout effect (useLayoutEffect or componentDidMount / componentDidUpdate) get processed synchronously by React before it yields to the browser to paint. This is done so that components can adjust their layout (e.g. position and size a tooltip) without any visible shifting being seen by users. This type of update is often called a "nested update" or a "cascading update".
Because they delay paint, nested updates are considered expensive and should be avoided when possible. For example, effects that do not impact layout (e.g. adding event handlers, logging impressions) can be safely deferred to the passive effect phase by using useEffect instead.
This PR updates the Profiler API to explicitly flag nested updates so they can be monitored for and avoided when possible.
Implementation:
I considered a few approaches for this.
Add a new callback (e.g. onNestedUpdateScheduled) to the Profiler that gets called when a nested updates gets scheduled.
Add an additional boolean parameter to the end of existing callbacks (e.g. wasNestedUpdate).
Update the phase param to add an additional variant: "mount", "update", or "nested-update" (new).
I think the third option makes for the best API so that's what I've implemented in this PR.
Because the Profiler API is stable, this change will need to remain behind a feature flag until v18. I've turned the feature flag on for Facebook builds though after confirming that Web Speed does not currently make use of the phase parameter.
Quirks:
One quirk about the implementation I've chosen is that errors thrown during the layout phase are also reported as nested updates. I believe this is appropriate since these errors get processed synchronously and block paint. Errors thrown during render or from within passive effects are not affected by this change.
This reverts commits bcca5a6ca7 and ffb749c95e, although neither revert cleanly since methods have been moved between the work-loop and commit-work files. This commit is a mostly manual effort of undoing the changes.
* 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
* Refactor Flight to require a module reference to be brand checked
This exposes a host environment (bundler) specific hook to check if an
object is a module reference. This will be used so that they can be passed
directly into Flight without needing additional wrapper objects.
* Emit module references as a special type of value
We already have JSON and errors as special types of "rows". This encodes
module references as a special type of row value. This was always the
intention because it allows those values to be emitted first in the stream
so that as a large models stream down, we can start preloading as early
as possible.
We preload the module when they resolve but we lazily require them as they
are referenced.
* Emit module references where ever they occur
This emits module references where ever they occur. In blocks or even
directly in elements.
* Don't special case the root row
I originally did this so that a simple stream is also just plain JSON.
However, since we might want to emit things like modules before the root
module in the stream, this gets unnecessarily complicated. We could add
this back as a special case if it's the first byte written but meh.
* Update the protocol
* Add test for using a module reference as a client component
* Relax element type check
Since Flight now accepts a module reference as returned by any bundler
system, depending on the renderer running. We need to drastically relax
the check to include all of them. We can add more as we discover them.
* Move flow annotation
Seems like our compiler is not happy with stripping this.
* Some bookkeeping bug
* Can't use the private field to check
* Move traversal logic to ReactFiberCommitWork
The current traversal logic is spread between ReactFiberWorkLoop and
ReactFiberCommitWork, and it's a bit awkward, especially when
refactoring. Idk the ideal module structure, so for now I'd rather keep
it all in one file.
* Traverse commit phase effects iteratively
We suspect that using the JS stack to traverse through the tree in the
commit phase is slower than traversing iteratively.
I've kept the recursive implementation behind a flag, both so we have
the option to run an experiment comparing the two, and so we can revert
it easily later if needed.
Reading or writing a ref value during render is only safe if you are implementing the lazy initialization pattern.
Other types of reading are unsafe as the ref is a mutable source.
Other types of writing are unsafe as they are effectively side effects.
This change also refactors useTransition to no longer use a ref hook, but instead manage its own (stable) hook state.
* update all facebook.github.io links
* facebookincubator links : update some outdated links and fix two other broken links where they are actually the latest updated ones
This PR double invokes effects in __DEV__ mode.
We are thinking about unmounting layout and/or passive effects for a hidden tree. To understand potential issues with this, we want to double invoke effects. This PR changes the behavior in DEV when an effect runs from create() to create() -> destroy() -> create(). The effect cleanup function will still be called before the effect runs in both dev and prod. (Note: This change is purely for research for now as it is likely to break real code.)
**Note: The change is fully behind a flag and does not affect any of the code on npm.**
* 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>
The behavior of error boundaries for passive effects that throw during cleanup was recently changed so that React ignores boundaries which are also unmounting in favor of still-mounted boundaries. This commit implements that same behavior for layout effects (useLayoutEffect, componentWillUnmount, and ref-detachment).
The new, skip-unmounting-boundaries behavior is behind a feature flag (`skipUnmountedBoundaries`).
These stacks improve the profiler data but they're expensive to generate and generating them can also cause runtime errors in larger applications (although an exact repro has been hard to nail down). Removing them for now. We can revisit adding them after this profiler has been integrated into the DevTools extension and we can generate them lazily.
* Make enableSchedulingProfiler flag static
* Copied debug tracing and scheduler profiling to .new fork and updated feature flags
* Move profiler component stacks behind a feature flag
* Make enableSchedulingProfiler static for profiling+experimental builds
* Copied debug tracing and scheduler profiling to .new fork
* Updated test @gate conditions
High level breakdown of this commit:
* Add a enableSchedulingProfiling feature flag.
* Add functions that call User Timing APIs to a new SchedulingProfiler file. The file follows DebugTracing's structure.
* Add user timing marks to places where DebugTracing logs.
* Add user timing marks to most other places where @bvaughn's original draft DebugTracing branch marks.
* Tests added
* More context (and discussions with @bvaughn) available at our internal PR MLH-Fellowship#11 and issue MLH-Fellowship#5.
Similar to DebugTracing, we've only added scheduling profiling calls to the old reconciler fork.
Co-authored-by: Kartik Choudhary <kartik.c918@gmail.com>
Co-authored-by: Kartik Choudhary <kartikc.918@gmail.com>
Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com>
* Initial currentLanePriority implementation
* Minor updates from review
* Fix typos and enable flag
* Fix feature flags and lint
* Fix simple event tests by switching to withSuspenseConfig
* Don't lower the priority of setPending in startTransition below InputContinuous
* Move currentUpdateLanePriority in commit root into the first effect block
* Refactor requestUpdateLane to log for priority mismatches
Also verifies that the update lane priority matches the scheduler lane priority before using it
* Fix four tests by adding ReactDOM.unstable_runWithPriority
* Fix partial hydration when using update lane priority
* Fix partial hydration when using update lane priority
* Rename feature flag and only log for now
* Move unstable_runWithPriority to ReactFiberReconciler
* Add unstable_runWithPriority to ReactNoopPersistent too
* Bug fixes and performance improvements
* Initial currentLanePriority implementation
* Minor updates from review
* Fix typos and enable flag
* Remove higherLanePriority from ReactDOMEventReplaying.js
* Change warning implementation and startTransition update lane priority
* Inject reconciler functions to avoid importing src/
* Fix feature flags and lint
* Fix simple event tests by switching to withSuspenseConfig
* Don't lower the priority of setPending in startTransition below InputContinuous
* Move currentUpdateLanePriority in commit root into the first effect block
* Refactor requestUpdateLane to log for priority mismatches
Also verifies that the update lane priority matches the scheduler lane priority before using it
* Fix four tests by adding ReactDOM.unstable_runWithPriority
* Fix partial hydration when using update lane priority
* Fix partial hydration when using update lane priority
* Rename feature flag and only log for now
* Move unstable_runWithPriority to ReactFiberReconciler
* Bug fixes and performance improvements
* Remove higherLanePriority from ReactDOMEventReplaying.js
* Change warning implementation and startTransition update lane priority
* Inject reconciler functions to avoid importing src/
* Fixes from bad rebase