* [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
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 PR exports a new top-level API, getInspectorDataForInstance, for React Native (both development and production). Although this change adds a new export to the DEV bundle, it only impacts the production bundle for internal builds (not what's published to NPM).
We don't need this anymore because we flush in a microtask.
This should allow us to remove the logic in the event system that
tracks nested event dispatches.
I added a test to confirm that nested event dispatches don't triggger
a synchronous flush, like they would if we wrapped them `flushSync`. It
already passed; I added it to prevent a regression.
* The exported '<React.StrictMode>' tag remains the same and opts legacy subtrees into strict mode level one ('mode == StrictModeL1'). This mode enables DEV-only double rendering, double component lifecycles, string ref warnings, legacy context warnings, etc. The primary purpose of this mode is to help detected render phase side effects. No new behavior. Roots created with experimental 'createRoot' and 'createBlockingRoot' APIs will also (for now) continue to default to strict mode level 1.
In a subsequent commit I will add support for a 'level' attribute on the '<React.StrictMode>' tag (as well as a new option supported by ). This will be the way to opt into strict mode level 2 ('mode == StrictModeL2'). This mode will enable DEV-only double invoking of effects on initial mount. This will simulate future Offscreen API semantics for trees being mounted, then hidden, and then shown again. The primary purpose of this mode is to enable applications to prepare for compatibility with the new Offscreen API (more information to follow shortly).
For now, this commit changes no public facing behavior. The only mechanism for opting into strict mode level 2 is the pre-existing 'enableDoubleInvokingEffects' feature flag (only enabled within Facebook for now).
* Renamed strict mode constants
StrictModeL1 -> StrictLegacyMode and StrictModeL2 -> StrictEffectsMode
* Renamed tests
* Split strict effects mode into two flags
One flag ('enableStrictEffects') enables strict mode level 2. It is similar to 'debugRenderPhaseSideEffectsForStrictMode' which enables srtict mode level 1.
The second flag ('createRootStrictEffectsByDefault') controls the default strict mode level for 'createRoot' trees. For now, all 'createRoot' trees remain level 1 by default. We will experiment with level 2 within Facebook.
This is a prerequisite for adding a configurable option to 'createRoot' that enables choosing a different StrictMode level than the default.
* Add StrictMode 'unstable_level' prop and createRoot 'unstable_strictModeLevel' option
New StrictMode 'unstable_level' prop allows specifying which level of strict mode to use. If no level attribute is specified, StrictLegacyMode will be used to maintain backwards compatibility. Otherwise the following is true:
* Level 0 does nothing
* Level 1 selects StrictLegacyMode
* Level 2 selects StrictEffectsMode (which includes StrictLegacyMode)
Levels can be increased with nesting (0 -> 1 -> 2) but not decreased.
This commit also adds a new 'unstable_strictModeLevel' option to the createRoot and createBatchedRoot APIs. This option can be used to override default behavior to increase or decrease the StrictMode level of the root.
A subsequent commit will add additional DEV warnings:
* If a nested StrictMode tag attempts to explicitly decrease the level
* If a level attribute changes in an update
* RN: Implement `sendAccessibilityEvent` on HostComponent
Implement `sendAccessibilityEvent` on HostComponent for Fabric and non-Fabric RN.
Currently the Fabric version is a noop and non-Fabric uses
AccessibilityInfo directly. The Fabric version will be updated once
native Fabric Android/iOS support this method in the native UIManager.
* Move methods out of HostComponent
* Properly type dispatchCommand and sendAccessibilityEvent handle arg
* Implement Fabric side of sendAccessibilityEvent
* Add tests: 1. Fabric->Fabric, 2. Paper->Fabric, 3. Fabric->Paper, 4. Paper->Paper
* Fix typo: ReactFaricEventTouch -> ReactFabricEventTouch
* fix flow types
* prettier
* Add feature flag
* Split stack from current fiber
You can get stack from any fiber, not just current.
* Refactor description of component frames
These should use fiber tags for switching. This also puts the relevant code
behind DEV flags.
* We no longer expose StrictMode in component stacks
They're not super useful and will go away later anyway.
* Update tests
Context is no longer part of SSR stacks. This was already the case on the
client.
forwardRef no longer is wrapped on the stack. It's still in getComponentName
but it's probably just noise in stacks. Eventually we'll remove the wrapper
so it'll go away anyway. If we use native stack frames they won't have this
extra wrapper.
It also doesn't pick up displayName from the outer wrapper. We could maybe
transfer it but this will also be fixed by removing the wrapper.
* Forward displayName onto the inner function for forwardRef and memo in DEV
This allows them to show up in stack traces.
I'm not doing this for lazy because lazy is supposed to be called on the
consuming side so you shouldn't assign it a name on that end. Especially
not one that mutates the inner.
* Use multiple instances of the fake component
We mutate the inner component for its name so we need multiple copies.
* Enable prefer-const rule
Stylistically I don't like this but Closure Compiler takes advantage of
this information.
* Auto-fix lints
* Manually fix the remaining callsites
* Rename lower case isomorphic default exports modules to upper case named exports
We're somewhat inconsistent here between e.g. ReactLazy and memo.
Let's pick one.
This also moves the responder, fundamental, scope creators from shared
since they're isomorphic and same as the other creators.
* Move some files that are specific to the react-reconciler from shared
Individual renderers are allowed to deep require into the reconciler.
* Move files specific to react-dom from shared
react-interactions is right now dom specific (it wasn't before) so we can
type check it together with other dom stuff. Avoids the need for
a shared ReactDOMTypes to be checked by RN for example.
* Move ReactWorkTags to the reconciler
* Move createPortal to export from reconciler
Otherwise Noop can't access it since it's not allowed deep requires.
* Require deep for reconcilers
* Delete inline* files
* Delete react-reconciler/persistent
This no longer makes any sense because it react-reconciler takes
supportsMutation or supportsPersistence as options. It's no longer based
on feature flags.
* Fix jest mocking
* Fix Flow strategy
We now explicitly list which paths we want to be checked by a renderer.
For every other renderer config we ignore those paths.
Nothing is "any" typed. So if some transitive dependency isn't reachable
it won't be accidentally "any" that leaks.
* Revert "Revert "Dispatch commands to both UIManagers from both renderers (#17211)" (#17232)"
This reverts commit d0fc0ba0a6.
* Clean up another __DEV__ warning check
* Replace all warning/lowPriWarning with console calls
* Replace console.warn/error with a custom wrapper at build time
* Fail the build for console.error/warn() where we can't read the stack
* prep for codemod
* prep warnings
* rename lint rules
* codemod for ifs
* shim www functions
* Handle more cases in the transform
* Thanks De Morgan
* Run the codemod
* Delete the transform
* Fix up confusing conditions manually
* Fix up www shims to match expected API
* Also check for low-pri warning in the lint rule
* Replace Babel plugin with an ESLint plugin
* Fix ESLint rule violations
* Move shared conditions higher
* Test formatting nits
* Tweak ESLint rule
* Bugfix: inside else branch, 'if' tests are not satisfactory
* Use a stricter check for exactly if (__DEV__)
This makes it easier to see what's going on and matches dominant style in the codebase.
* Fix remaining files after stricter check
* [react-native] Use path-based imports instead of Haste for the RN renderer
To move React Native to standard path-based imports instead of Haste, the RN renderer that is generated from the code in this repo needs to use path-based imports as well since the generated code is vendored by RN. This commit makes it so the interface between the generated renderers and RN does not rely on Haste and instead uses a private interface explicitly defined by RN. This inverts control of the abstraction so that RN decides the internals to export rather than React deciding what to import.
On RN's side, a new module named `react-native/Libraries/ReactPrivate/ReactNativePrivateInterface` explicitly exports the modules used by the renderers in this repo. (There is also a private module for InitializeCore so that we can import it just for the side effects.) On React's side, the various renderer modules access RN internals through the explicit private interface.
The Rollup configuration becomes slimmer since the only external package is now `react-native`, and the individual modules are instead listed out in `ReactNativePrivateInterface`.
Task description: https://github.com/facebook/react-native/issues/24770
Sister RN PR (needs to land before this one): https://github.com/facebook/react-native/pull/24782
Test Plan: Ran unit tests and Flow in this repo. Generated the renderers and manually copied them over to the RN repo. Ran the RN tests and launched the RNTester app.
* Access natively defined "nativeFabricUIManager" instead of importing it
Some places in the Fabric renderers access `nativeFabricUIManager` (a natively defined global) instead of importing UIManager. While this is coupling across repos that depends on the timing of events, it is necessary until we have a way to defer top-level imports to run after `nativeFabricUIManager` is defined. So for consistency we use `nativeFabricUIManager` everywhere (see the comment in https://github.com/facebook/react/pull/15604#pullrequestreview-236842223 for more context).
* Add Batched Mode
React has an unfortunate quirk where updates are sometimes synchronous
-- where React starts rendering immediately within the call stack of
`setState` — and sometimes batched, where updates are flushed at the
end of the current event. Any update that originates within the call
stack of the React event system is batched. This encompasses most
updates, since most updates originate from an event handler like
`onClick` or `onChange`. It also includes updates triggered by lifecycle
methods or effects. But there are also updates that originate outside
React's event system, like timer events, network events, and microtasks
(promise resolution handlers). These are not batched, which results in
both worse performance (multiple render passes instead of single one)
and confusing semantics.
Ideally all updates would be batched by default. Unfortunately, it's
easy for components to accidentally rely on this behavior, so changing
it could break existing apps in subtle ways.
One way to move to a batched-by-default model is to opt into Concurrent
Mode (still experimental). But Concurrent Mode introduces additional
semantic changes that apps may not be ready to adopt.
This commit introduces an additional mode called Batched Mode. Batched
Mode enables a batched-by-default model that defers all updates to the
next React event. Once it begins rendering, React will not yield to
the browser until the entire render is finished.
Batched Mode is superset of Strict Mode. It fires all the same warnings.
It also drops the forked Suspense behavior used by Legacy Mode, in favor
of the proper semantics used by Concurrent Mode.
I have not added any public APIs that expose the new mode yet. I'll do
that in subsequent commits.
* Suspense in Batched Mode
Should have same semantics as Concurrent Mode.
* Use RootTag field to configure type of root
There are three types of roots: Legacy, Batched, and Concurrent.
* flushSync should not flush batched work
Treat Sync and Batched expiration times separately. Only Sync updates
are pushed to our internal queue of synchronous callbacks.
Renamed `flushImmediateQueue` to `flushSyncCallbackQueue` for clarity.
* Adding ReactNative.setNativeProps that takes a ref
* Adding test for components rendered with Fabric with Paper's setNativeProps
* Fixing flow types
* Fix prettier
* Rename ReactNativeSetNativeProps.js to be more general
Whenever we do this, Rollup needs to materialize this as an object.
This causes it to also add the Babel compatibility property which is
unnecessary bloat. However, since when we use these, we leak the object
this often also deopts any compiler optimizations.
If we really need an object we should export default an object.
Currently there is an exception for DOMTopLevelEventTypes since
listing out the imports is a PITA and it doesn't escape so it should
get properly inlined. We should probably move to a different pattern
to avoid this for consistency though.
* Deprecate findDOMNode in StrictMode
There are two scenarios. One is that we pass a component instance that is
already in strict mode or the node that we find is in strict mode if
an outer component renders into strict mode.
I use a separate method findHostInstanceWithWarning for this so that
a) I can pass the method name (findDOMNode/findNodeHandle).
b) Can ignore this warning in React Native mixins/NativeComponent that use this helper.
I don't want to expose the fiber to the renderers themselves.
* Replace some namespace imports
* Simplify the controlled component injection
* Simplify the batching injection
* Simplify the component tree injection
* Use %s in the console calls
* Add shared/warningWithStack
* Convert some warning callsites to warningWithStack
* Use warningInStack in shared utilities and remove unnecessary checks
* Replace more warning() calls with warningWithStack()
* Fixes after rebase + use warningWithStack in react
* Make warning have stack by default; warningWithoutStack opts out
* Forbid builds that may not use internals
* Revert newly added stacks
I changed my mind and want to keep this PR without functional changes. So we won't "fix" any warnings that are already missing stacks. We'll do it in follow-ups instead.
* Fix silly find/replace mistake
* Reorder imports
* Add protection against warning argument count mismatches
* Address review
* Fix getComponentName() for types with nested $$typeof
* Temporarily remove Profiler ID from messages
* Change getComponentName() signature to take just type
It doesn't actually need the whole Fiber.
* Remove getComponentName() forks in isomorphic and SSR
* Remove unnecessary .type access where we already have a type
* Remove unused type
* Refactor ReactDebugCurrentFiber to use named exports
This makes the difference between it and ReactFiberCurrentFrame a bit clearer.
ReactDebugCurrentFiber is Fiber's own implementation.
ReactFiberCurrentFrame is the thing that holds a reference to the current implementation and delegates to it.
* Unify ReactFiberComponentTreeHook and ReactDebugCurrentFiber
Conceptually they're very related.
ReactFiberComponentTreeHook contains implementation details of reading Fiber's stack (both in DEV and PROD).
ReactDebugCurrentFiber contained a reference to the current fiber, and used the above utility.
It was confusing when to use which one. Colocating them makes it clearer what you could do with each method.
In the future, the plan is to stop using these methods explicitly in most places, and instead delegate to a warning system that includes stacks automatically. This change makes future refactorings simpler by colocating related logic.
* Rename methods to better reflect their meanings
Clarify which are DEV or PROD-only.
Clarify which can return null.
I believe the "work in progress only" was a mistake. I introduced it because I wasn't sure what guarantees we have around .return. But we know for sure that following a .return chain gives us an accurate stack even if we get into WIP trees because we don't have reparenting. So it's fine to relax that naming.
* Rename ReactDebugCurrentFiber -> ReactCurrentFiber
It's not completely DEV-only anymore.
Individual methods already specify whether they work in DEV or PROD in their names.