When a Fragment has no children, React would consider scrolling to
siblings first and then to parents. However, React only considered
`HostComponent` for the siblings.
Since we already have a heuristic for scrolling to `HostText`, we can
reuse that same heuristic.
---------
Co-authored-by: Hanzo Dev <[email protected]>
An alternative to https://github.com/facebook/react/pull/36135
___
Adds experimental support for `parentEnter` and `parentExit` on nested
`<ViewTransition>` components, so descendants can animate when an
ancestor boundary enters or exits.
This is gated behind the `enableViewTransitionParentEnterExit` feature
flag.
Addresses the use case of animating individual items in a list (such as
sliding inactive feed posts off-screen) when a parent `<ViewTransition>`
is the one being added or removed during the update.
Today, `exit`/ `enter` only fire on the `<ViewTransition>` that was
directly mounted or unmounted. Nested boundaries in the same subtree are
not visited for their own `exit`/`enter` when the whole tree goes away
as one unit, which is usually what you want. But some cases need an
explicit opt-in to nested animations.
This PR adds `parentEnter`/`parentExit`, and their handlers
`onParentEnter`/`onParentExit`. When an ancestor runs a real `enter` or
`exit`, React walks descendants and activates boundaries that define the
nested props. This only continues through `<ViewTransition>` nodes that
define `parentEnter` / `parentExit` / `onParentEnter` / `onParentExit` /
`onGestureParentEnter` / `onGestureParentExit`, otherwise the chaining
stops. A boundary’s own `enter`/`exit` still do not run when only an
ancestor animates. So it is possible to have different animations on the
same `<ViewTransition>` for `enter` vs `parentEnter`.
```jsx
function ExampleOnAncestorExit() {
return (
// This boundary exits — starts the parentExit walk
<ViewTransition exit="panel-exit">
<div>
{/* ✓ Activates: parentExit set. Walk continues inside */}
<ViewTransition parentExit="slide-out">
<div>
{/* ✗ parentEnter is not consulted on an exit walk */}
<ViewTransition parentEnter="slide-in">
...
</ViewTransition>
{/* ✗ Chain broken: no parentExit or onParentExit */}
<ViewTransition>
<div>
{/* ✗ Never reached on this path, blocked by parent above */}
<ViewTransition parentExit="slide-out">
...
</ViewTransition>
</div>
</ViewTransition>
{/* ✓ Handler-only chain: onParentExit continues the walk */}
<ViewTransition onParentExit={() => animateOut()}>
<ViewTransition onParentExit={() => animateDeepOut()}>
...
</ViewTransition>
</ViewTransition>
{/* ✓ Activates: sibling under the chain */}
<ViewTransition parentExit="slide-out">
...
</ViewTransition>
</div>
</ViewTransition>
</div>
</ViewTransition>
);
}
```
## Summary
Mostly changing the casting syntax from `(x: Y)` to `x as Y`, as the old
syntax was deprecated and always causing an error in Flow
## How did you test this change?
`yarn flow-ci`
## Summary
Notable changes:
- Only $FlowFixMe, $FlowExpectedError suppression comments are supported
- All suppression comments need error code
- A lot of new invalid-compare and constant-condition errors suppressed.
These errors reveal potential logical bugs
## How did you test this change?
`yarn flow-ci`
For JavaScript runtimes that do not have
[`Reflect`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect)
supported, we had a fall back that was calling the constructor with
overridden `this` context via
```
fn.apply(Fake.prototype);
```
In ES6, it is required to call constructor only with the `new` keyword,
otherwise the runtime is expected to throw a corresponding TypeError:
```
TypeError: Class constructor <> cannot be invoked without 'new'
```
We've observed this error in Hermes runtime, but this is applicable to
V8 or any other runtime. The only reason why V8 wasn't affected is
because it implemented Reflect APIs.
Instead of the incorrect call, we will fall back to calling `new fn()`,
but with a temporary patched prototype of the class, which would make a
trap out of the setter for `props` object. We use the same approach when
`Reflect` APIs are available, but instead of modifying the prototype, we
pass the fake context:
https://github.com/facebook/react/blob/878b6fb60ce5a2347ec1bfa0741edf0b2d08f157/packages/shared/ReactComponentStackFrame.js#L129-L148
---
See tests implemented. Without the changes, the test would fail with the
`TypeError` mentioned above.
Adds a `enableInfiniteRenderLoopDetectionForceThrow` flag, which changes
the detection mechanism behaviour to start throwing, when the loop is
observed. By default, the value is set to `false`, also made dynamic
from the start for `www`.
PR #35951 added FB_WWW_DEV builds for eslint-plugin-react-hooks to get
www-specific feature flag values. However, the FB_WWW build uses the
full ReactFeatureFlags.www.js fork, which contains:
const dynamicFeatureFlags = require('ReactFeatureFlags');
This is a www Haste module that only exists in the www runtime. Rollup
can't tree-shake CJS require() calls (they're assumed side-effectful),
so the bare require('ReactFeatureFlags') survives in the build output
even though the eslint plugin only uses the static eprh_* exports.
When the built artifact is synced to www at
scripts/lint/eslint/rules/eslint-plugin-react-hooks/index.js, Node.js
fails with "Cannot find module 'ReactFeatureFlags'" because Haste
modules aren't available in the Node.js lint environment.
Create a dedicated fork (ReactFeatureFlags.eslint-plugin.www.js) that
exports only the static eprh_* flags with www values, without the
require('ReactFeatureFlags') dependency. Wire it up in forks.js for the
eslint-plugin-react-hooks entry point.
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
Co-authored-by: Eugene Choi <[email protected]>
We're currently hardcoding experimental options to
`eslint-plugin-react-hooks`. This blocks the release on features that
might not be ready.
This PR extends the ReactFeatureFlag infra to support flags for
`eslint-plugin-react-hooks`. An alternative would be to create a
separate flag system for build tools, but for now we have a small number
of these and reusing existing infra seems like the simplest approach.
I ran a full `yarn build` and checked the output resolved the flag
values as expected:
_build/oss-stable-semver/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js_
```js
var eprh_enableUseKeyedStateCompilerLint = false;
var eprh_enableVerboseNoSetStateInEffectCompilerLint = false;
var eprh_enableExhaustiveEffectDependenciesCompilerLint = 'off';
```
_build/facebook-www/ESLintPluginReactHooks-dev.classic.js_
```js
var eprh_enableUseKeyedStateCompilerLint = true;
var eprh_enableVerboseNoSetStateInEffectCompilerLint = true;
var eprh_enableExhaustiveEffectDependenciesCompilerLint = 'extra-only';
```
---------
Co-authored-by: lauren <[email protected]>
## Summary
Enables Basic View Transition support for React Native Fabric renderer.
**Implemented:**
- Added FabricUIManager bindings for view transition methods:
`applyViewTransitionName`, `startViewTransition`
- Implemented `startViewTransition` with proper callback orchestration
(mutation → layout → afterMutation → spawnedWork → passive)
- Added fallback behavior that flushes work synchronously when Fabric's
`startViewTransition` returns null (e.g., when the ViewTransition
ReactNativeFeatureFlag is not enabled)
- Added Flow type declarations for new FabricUIManager methods
- Stubbed with `__DEV__` warnings for all the other view transition
config functions that are not yet implemented
This allows React Native apps using Fabric to leverage the View
Transition API for coordinated animations during state transitions, with
graceful degradation when the native side doesn't support it.
Below are diagrams of proposed architecture in fabric, and observation
of what/when config functions get called during a basic shared
transition example
<img width="2290" height="1529" alt="Untitled-2026-03-19-1240"
src="https://github.com/user-attachments/assets/192c9169-bc25-449c-a33b-dfec67179e7f"
/>
## How did you test this change?
- [x] `yarn flow fabric` - Flow type checks pass
- [x] `yarn lint` - Lint checks pass
- [x] Manually tested in Android catalyst app with
`enableViewTransition` and `enableViewTransitionForPersistenceMode `in
`ReactFeatureFlags.test-renderer.native-fb.js` and View Transition
enabled via ReactNativeFeatureFlag
- [x] Verified in the minified `ReactFabric-dev.fb.js` that the 'shim'
config functions are not included
- [x] Verified fallback behavior logs warning in `__DEV__` and flushes
work synchronously when ViewTransition flag isn't enabled in Fabric
## Summary
This flag enables React's integration with the browser [Trusted Types
API](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API).
The Trusted Types API is a browser security feature that helps prevent
DOM-based XSS attacks. When a site enables Trusted Types enforcement via
`Content-Security-Policy: require-trusted-types-for 'script'`, the
browser requires that values passed to DOM injection sinks (like
`innerHTML`) are typed objects (`TrustedHTML`, `TrustedScript`,
`TrustedScriptURL`) created through developer-defined sanitization
policies, rather than raw strings.
### What changed
Previously, React always coerced values to strings (via `'' + value`)
before passing them to DOM APIs like `setAttribute` and `innerHTML`.
This broke Trusted Types because it converted typed objects into plain
strings, which the browser would then reject under Trusted Types
enforcement.
React now passes values directly to DOM APIs without string coercion,
preserving Trusted Types objects so the browser can validate them. This
applies to `dangerouslySetInnerHTML`, all HTML and SVG attributes, and
URL attributes (`href`, `action`, etc).
### Before (broken)
Using Trusted Types with something like`dangerouslySetInnerHTML` would
throw:
```js
const sanitizer = trustedTypes.createPolicy('sanitizer', {
createHTML: (input) => DOMPurify.sanitize(input),
});
function Comment({text}) {
const clean = sanitizer.createHTML(text);
// clean is a TrustedHTML object, but React would call '' + clean,
// converting it back to a plain string before setting innerHTML.
// Under Trusted Types enforcement, the browser rejects the string:
//
// TypeError: Failed to set 'innerHTML' on 'Element':
// This document requires 'TrustedHTML' assignment.
return <div dangerouslySetInnerHTML={{__html: clean}} />;
}
```
### After (works)
React now passes the TrustedHTML object directly to the DOM without
stringifying it:
```js
const policy = trustedTypes.createPolicy('sanitizer', {
createHTML: (input) => DOMPurify.sanitize(input),
});
function Comment({text}) {
// TrustedHTML objects are passed directly to innerHTML
return <div dangerouslySetInnerHTML={{__html: policy.createHTML(text)}} />;
}
function UserProfile({bio}) {
// String attribute values also preserve Trusted Types objects
return <div data-bio={policy.createHTML(bio)} />;
}
```
## Non-breaking change
- Sites using Trusted Types: React no longer breaks Trusted Types enforcement. TrustedHTML and TrustedScriptURL objects passed through React props are forwarded to the DOM without being stringified.
- Sites not using Trusted Types: No behavior change. DOM APIs accept both strings and Trusted Types objects, so removing the explicit string coercion is functionally identical.
Small optimization for useEffectEvent. Not sure we even need a flag for
it, but it will be a nice killswitch.
As an added benefit, it fixes a bug when `enableViewTransition` is on,
where we were not updating the useEffectEvent callback when a tree went
from hidden to visible.
## Overview
Adds a feature flag `enableParallelTransitions` to experiment with
engantling transitions less often.
## Motivation
Currently we over-entangle transition lanes.
It's a common misunderstanding that React entangles all transitions,
always. We actually will complete transitions independently in many
cases. For example, [this
codepen](https://codepen.io/GabbeV/pen/pvyKBrM) from
[@gabbev](https://bsky.app/profile/gabbev.bsky.social/post/3m6uq2abihk2x)
shows transitions completing independently.
However, in many cases we entangle when we don't need to, instead of
letting the independent transitons complete independently. We still want
to entangle for updates that happen on the same queue.
## Example
As an example of what this flag would change, consider two independent
counter components:
```js
function Counter({ label }) {
const [count, setCount] = useState(0);
return (
<div>
<span>{use(readCache(`${label} ${count}`))} </span>
<Button
action={() => {
setCount((c) => c + 1);
}}
>
Next {label}
</Button>
</div>
);
}
```
```js
export default function App() {
return (
<>
<Counter label="A" />
<Counter label="B" />
</>
);
}
```
### Before
The behavior today is to entange them, meaning they always commit
together:
https://github.com/user-attachments/assets/adead60e-8a98-4a20-a440-1efdf85b2142
### After
In this experiment, they will complete independently (if they don't
depend on each other):
https://github.com/user-attachments/assets/181632b5-3c92-4a29-a571-3637f3fab8cd
## Early Research
This change is in early research, and is not in the experimental
channel. We're going to experiment with this at Meta to understand how
much of a breaking change, and how beneficial it is before commiting to
shipping it in experimental and beyond.
In https://github.com/facebook/react/pull/35646 I thought there was a
bug in trusted types, but the bug is in jsdom.
For trusted types we still want to check the coersion and throw for a
good dev warning, but prod will also throw becuase the browser will
implicitly coerce to a string. This ensures there's no behavior
difference between dev and prod.
So the right fix is to add in the JSDOM hack that's used in
`ReactDOMSelect-test.js`.
This PR adds text node support to FragmentInstance operations, allowing
fragment refs to properly handle fragments that contain text nodes
(either mixed with elements or text-only).
Not currently adding/removing new text nodes as we don't need to track
them for events or observers in DOM. Will follow up on this and with
Fabric support.
## Support through parent element
- `dispatchEvent`
- `compareDocumentPosition`
- `getRootNode`
## Support through Range API
- `getClientRects`: Uses Range to calculate bounding rects for text
nodes
- `scrollIntoView`: Uses Range to scroll to text node positions directly
## No support
- `focus`/`focusLast`/`blur`: Noop for text-only fragments
- `observeUsing`: Warns for text-only fragments in DEV
- `addEventListener`/`removeEventListener`: Ignores text nodes, but
still works on Fragment level through `dispatchEvent`
Follow-up to https://github.com/facebook/react/pull/34665.
Already gated on `enableProfilerTimer` everywhere, which is only enabled
for `__PROFILE__`, except for Flight should be unified in a future.
Stacked on #35556 and #35559.
Given that we don't automatically clean up all view transition
animations since #35337 and browsers are buggy, it's important that you
clean up any `Animation` started manually from the events. However,
there was no clean up function for when the View Transition is forced to
stop. This also makes it harder to clean up custom timers etc too.
This lets you return a clean up function from all the events on
`<ViewTransition>`.
When dealing with optimistic state, a common problem is not knowing the
id of the thing we're waiting on. Items in lists need keys (and single
items should often have keys too to reset their state). As a result you
have to generate fake keys. It's a pain to manage those and when the
real item comes in, you often end up rendering that with a different
`key` which resets the state of the component tree. That in turns works
against the grain of React and a lot of negatives fall out of it.
This adds a special `optimisticKey` symbol that can be used in place of
a `string` key.
```js
import {optimisticKey} from 'react';
...
const [optimisticItems, setOptimisticItems] = useOptimistic([]);
const children = savedItems.concat(
optimisticItems.map(item =>
<Item key={optimisticKey} item={item} />
)
);
return <div>{children}</div>;
```
The semantics of this `optimisticKey` is that the assumption is that the
newly saved item will be rendered in the same slot as the previous
optimistic items. State is transferred into whatever real key ends up in
the same slot.
This might lead to some incorrect transferring of state in some cases
where things don't end up lining up - but it's worth it for simplicity
in many cases since dealing with true matching of optimistic state is
often very complex for something that only lasts a blink of an eye.
If a new item matches a `key` elsewhere in the set, then that's favored
over reconciling against the old slot.
One quirk with the current algorithm is if the `savedItems` has items
removed, then the slots won't line up by index anymore and will be
skewed. We might be able to add something where the optimistic set is
always reconciled against the end. However, it's probably better to just
assume that the set will line up perfectly and otherwise it's just best
effort that can lead to weird artifacts.
An `optimisticKey` will match itself for updates to the same slot, but
it will not match any existing slot that is not an `optimisticKey`. So
it's not an `any`, which I originally called it, because it doesn't
match existing real keys against new optimistic keys. Only one
direction.
We already append `randomKey` to each handle name to prevent external
libraries from accessing and relying on these internals. But more
libraries recently have been getting around this by simply iterating
over the element properties and using a `startsWith` check.
This flag allows us to experiment with moving these handles to an
internal map.
This PR starts with the two most common internals, the props object and
the fiber. We can consider moving additional properties such as the
container root and others depending on perf results.
Follow up to #35022.
It's now replaced by the `defer` option.
Sounds like nobody is actually using this option, including Meta, so we
can just delete it.
We've long had the CPU suspense feature behind a flag under the terrible
API `unstable_expectedLoadTime={arbitraryNumber}`. We've known for a
long time we want it to just be `defer={true}` (or just `<Suspense
defer>` in the short hand syntax). So this adds the new name and warns
for the old name.
For only the new name, I also implemented SSR semantics in Fizz. It has
two effects here.
1) It renders the fallback before the content (similar to prerender)
allowing siblings to complete quicker.
2) It always outlines the result. When streaming this should really
happen naturally but if you defer a prerendered content it also implies
that it's expensive and should be outlined. It gives you a opt-in to
outlining similar to suspensey images and css but let you control it
manually.
I don't think we're ready to land this yet since we're using it to run
other experiments and our tests. I'm opening this PR to indicate intent
to disable and to ensure tests in other combinations still work. Such as
enableHalt without enablePostpone. I think we'll also need to rewrite
some tests that depend on enablePostpone to preserve some coverage.
The conclusion after this experiment is that try/catch around these are
too likely to block these signals and consider them error. Throwing
works for Hooks and `use()` because the lint rule can ensure that
they're not wrapped in try/catch. Throwing in arbitrary functions not
quite ecosystem compatible. It's also why there's `use()` and not just
throwing a Promise. This might also affect the Catch proposal.
The "prerender" for SSR that's supporting "Partial Prerendering" is
still there. This just disables the `React.postpone()` API for creating
the holes.
This PR adds a `unstable_reactFragments?: Set<FragmentInstance>`
property to DOM nodes that belong to a Fragment with a ref (top level
host components). This allows you to access a FragmentInstance from a
DOM node.
This is flagged behind `enableFragmentRefsInstanceHandles`.
The primary use case to unblock is reusing IntersectionObserver
instances. A fairly common practice is to cache and reuse
IntersectionObservers that share the same config, with a map of
node->callbacks to run for each entry in the IO callback. Currently this
is not possible with Fragment Ref `observeUsing` because the key in the
cache would have to be the `FragmentInstance` and you can't find it
without a handle from the node. This works now by accessing
`entry.target.fragments`.
This also opens up possibilities to use `FragmentInstance` operations in
other places, such as events. We can do
`event.target.unstable_reactFragments`, then access
`fragmentInstance.getClientRects` for example. In a future PR, we can
assign an event's `currentTarget` as the Fragment Ref for a more direct
handle when the event has been dispatched by the Fragment itself.
The first commit here implemented a handle only on observed elements.
This is awkward because there isn't a good way to document or expose
this temporary property. `element.fragments` is closer to what we would
expect from a DOM API if a standard was implemented here. And by
assigning it to all top-level nodes of a Fragment, it can be used beyond
the cached IntersectionObserver callback.
One tradeoff here is adding extra work during the creation of
FragmentInstances as well as keeping track of adding/removing nodes.
Previously we only track the Fiber on creation but here we add a
traversal which could apply to a large set of top-level host children.
The `element.unstable_reactFragments` Set can also be randomly ordered.