Skip to content

fix: catch parameter alias handling and snapshot updates for noBackwardRangeExtension#1

Closed
fresh3nough wants to merge 46 commits intomainfrom
fix/set-state-in-effect-hof-35910
Closed

fix: catch parameter alias handling and snapshot updates for noBackwardRangeExtension#1
fresh3nough wants to merge 46 commits intomainfrom
fix/set-state-in-effect-hof-35910

Conversation

@fresh3nough
Copy link
Owner

Summary

Fixes failing compiler snapshot tests introduced by the noBackwardRangeExtension changes for method call receivers.

Problem

The noBackwardRangeExtension heuristic in InferMutationAliasingRanges.ts walks backward through alias edges to detect function call results (identified by having maybeAliases). However, catch parameters are aliased to call results in try blocks via terminal Alias effects. This caused the heuristic to incorrectly treat catch parameters as independently-memoizable function call results, preventing backward range extension and causing:

  • 4 compiler crashes (Invariant: Expected a break target) when try-catch blocks got split across reactive scopes
  • 1 snapshot regression where catch-aliased values were incorrectly scoped separately

Fix

Track catch handler binding identifiers in AliasingState.catchParams. During the backward-alias walk in mutate(), stop traversal when a catch parameter is encountered. This prevents the heuristic from walking through catch parameter aliases to reach call results with maybeAliases.

Changes

  • InferMutationAliasingRanges.ts: Added catchParams: Set<Identifier> to AliasingState, populated from maybe-throw terminal Alias effects, and checked during the backward-alias heuristic walk
  • 8 snapshot updates: Updated .expect.md files for tests where the noBackwardRangeExtension change produces different but functionally correct memoization output (all eval outputs remain correct)

Tests fixed

  • try-catch-try-value-modified-in-catch (and propagate-scope-deps-hir-fork variant)
  • try-catch-try-value-modified-in-catch-escaping (and propagate-scope-deps-hir-fork variant)
  • try-catch-alias-try-values

Closes facebook#35902

jackpope and others added 30 commits February 11, 2026 09:26
Follow up to facebook#35630

We don't currently have any operations that depend on the updating of
text nodes added or removed after Fragment mount. But for the sake of
completeness and extending the ability to any other host configs, this
change calls `commitNewChildToFragmentInstance` and
`deleteChildFromFragmentInstance` on HostText fibers.

Both DOM and Fabric configs early return because we cannot attach event
listeners or observers to text. In the future, there could be some
stateful Fragment feature that uses text that could extend this.
Handles TODOs, small follow up refactors
…5763)

## Overview

While building the RSC sandboxes I notice error messages like:

>  An error occurred in the `<Offscreen>` component

This is an internal component so it should show either:

>  An error occurred in the `<Suspense>` component.

>  An error occurred in the `<Activity>` component.

It should only happen when there's a lazy in the direct child position
of a `<Suspense>` or `<Activity>` component.
…ren before removing (facebook#35775)

Currently, this silently removes the last child in the list, which
doesn't contain the `id`.
## Summary

ESLint v10.0.0 was released on February 7, 2026. The current
`peerDependencies` for `eslint-plugin-react-hooks` only allows up to
`^9.0.0`, which causes peer dependency warnings when installing with
ESLint v10.

This PR:

- Adds `^10.0.0` to the eslint peer dependency range
- Adds `eslint-v10` to devDependencies for testing
- Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9`
fixture)

ESLint v10's main breaking changes (removal of legacy eslintrc config,
deprecated context methods) don't affect this plugin - flat config is
already supported since v7.0.0, and the deprecated APIs already have
fallbacks in place.

## How did you test this change?

Ran the existing unit test suite:

```
cd packages/eslint-plugin-react-hooks && yarn test
```

All 5082 tests passed.
…ion (facebook#35795)

When the Flight Client resolves chunk references during model parsing,
it calls `transferReferencedDebugInfo` to propagate debug info entries
from referenced chunks to the parent chunk. Debug info on chunks is
later moved to their resolved values, where it is used by React DevTools
to show performance tracks and what a component was suspended by.

Debug chunks themselves (specifically `ReactComponentInfo`,
`ReactAsyncInfo`, `ReactIOInfo`, and their outlined references) are
metadata that is never rendered. They don't need debug info attached to
them. Without this fix, debug info entries accumulate on outlined debug
chunks via their references to other debug chunks (e.g. owner chains and
props deduplication paths). Since each outlined chunk's accumulated
entries are copied to every chunk that references it, this creates
exponential growth in deep component trees, which can cause the dev
server to hang and run out of memory.

This generalizes the existing skip of `transferReferencedDebugInfo` for
Element owner/stack references (which already recognizes that references
to debug chunks don't need debug info transferred) to all references
resolved during debug info resolution. It adds an
`isInitializingDebugInfo` flag set in `initializeDebugChunk` and
`resolveIOInfo`, which propagates through all nested
`initializeModelChunk` calls within the same synchronous stack. For the
async path, `waitForReference` captures the flag at call time into
`InitializationReference.isDebug`, so deferred fulfillments also skip
the transfer.
Tracks locations for reactive scope dependencies, both on the deps and
portions of the path. The immediate need for this is a non-public
experiment where we're exploring type-directed compilation, and
sometimes look up the types of expressions by location. We need to
preserve locations accurately for that to work, including the locations
of the deps.

## Test Plan

Locations for dependencies are not easy to test: i manually spot-checked
the new fixture to ensure that the deps look right. This is fine as
best-effort since it doesn't impact any of our core compilation logic, i
may fix forward if there are issues and will think about how to test.
…ok#35780)

## Summary

Add "RCTSelectableText" to the list of component names recognized as
being inside a text element, alongside "RCTText".

React Native's new text stack, tries to optimize and allows
differentiating between a custom TextView, with lower level control,
that can reuse the work performed during Fabric/Yoga layout, and a
native TextView, used for fidelity. On Android at least, the only place
we've needed native TextView for fidelity/native UX has been support for
`selectable` text, which has many unique UI interactions.

## How did you test this change?

When I patch this in, alongside
facebook/react-native#55552, we no longer see
warnings when we render text inside of RCTSelectableText component.

---------

Co-authored-by: Eli White <github@eli-white.com>
…yload (facebook#35776)

## Summary

Follow-up to vercel/next.js#89823 with the
actual changes to React.

Replaces the `JSON.parse` reviver callback in `initializeModelChunk`
with a two-step approach: plain `JSON.parse()` followed by a recursive
`reviveModel()` post-process (same as in Flight Reply Server). This
yields a **~75% speedup** in RSC chunk deserialization.

| Payload | Original (ms) | Walk (ms) | Speedup |
|---------|---------------|-----------|---------|
| Small (2 elements, 142B) | 0.0024 | 0.0007 | **+72%** |
| Medium (~12 elements, 914B) | 0.0116 | 0.0031 | **+73%** |
| Large (~90 elements, 16.7KB) | 0.1836 | 0.0451 | **+75%** |
| XL (~200 elements, 25.7KB) | 0.3742 | 0.0913 | **+76%** |
| Table (1000 rows, 110KB) | 3.0862 | 0.6887 | **+78%** |

## Problem

`createFromJSONCallback` returns a reviver function passed as the second
argument to `JSON.parse()`. This reviver is called for **every key-value
pair** in the parsed JSON. While the logic inside the reviver is
lightweight, the dominant cost is the **C++ → JavaScript boundary
crossing** — V8's `JSON.parse` is implemented in C++, and calling back
into JavaScript for every node incurs significant overhead.

Even a trivial no-op reviver `(k, v) => v` makes `JSON.parse` **~4x
slower** than bare `JSON.parse` without a reviver:

```
108 KB payload:
  Bare JSON.parse:    0.60 ms
  Trivial reviver:    2.95 ms  (+391%)
```

## Change

Replace the reviver with a two-step process:

1. `JSON.parse(resolvedModel)` — parse the entire payload in C++ with no
callbacks
2. `reviveModel` — recursively walk the resulting object in pure
JavaScript to apply RSC transformations

The `reviveModel` function includes additional optimizations over the
original reviver:
- **Short-circuits plain strings**: only calls `parseModelString` when
the string starts with `$`, skipping the vast majority of strings (class
names, text content, etc.)
- **Stays entirely in JavaScript** — no C++ boundary crossings during
the walk

## Results

You can find the related applications in the [Next.js PR
](vercel/next.js#89823 I've been testing this
on Next.js applications.

### Table as Server Component with 1000 items

Before:

```
    "min": 13.782875000000786,
    "max": 22.23400000000038,
    "avg": 17.116868530000083,
    "p50": 17.10766700000022,
    "p75": 18.50787499999933,
    "p95": 20.426249999998618,
    "p99": 21.814125000000786
```

After:

```
    "min": 10.963916999999128,
    "max": 18.096083000000363,
    "avg": 13.543286884999988,
    "p50": 13.58350000000064,
    "p75": 14.871791999999914,
    "p95": 16.08429099999921,
    "p99": 17.591458000000785
```

### Table as Client Component with 1000 items

Before:

```
    "min": 3.888875000000553,
    "max": 9.044959000000745,
    "avg": 4.651271475000067,
    "p50": 4.555749999999534,
    "p75": 4.966624999999112,
    "p95": 5.47754200000054,
    "p99": 6.109499999998661
````

After:

```
    "min": 3.5986250000005384,
    "max": 5.374291000000085,
    "avg": 4.142990245000046,
    "p50": 4.10570799999914,
    "p75": 4.392041999999492,
    "p95": 4.740084000000934,
    "p99": 5.1652500000000146
```

### Nested Suspense

Before:

```
  Requests:  200
  Min:       73ms
  Max:       106ms
  Avg:       78ms
  P50:       77ms
  P75:       80ms
  P95:       85ms
  P99:       94ms
```

After:

```
  Requests:  200
  Min:       56ms
  Max:       67ms
  Avg:       59ms
  P50:       58ms
  P75:       60ms
  P95:       65ms
  P99:       66ms
```

### Even more nested Suspense (double-level Suspense)

Before:

```
  Requests:  200
  Min:       159ms
  Max:       208ms
  Avg:       169ms
  P50:       167ms
  P75:       173ms
  P95:       183ms
  P99:       188ms
```

After:

```
  Requests:  200
  Min:       125ms
  Max:       170ms
  Avg:       134ms
  P50:       132ms
  P75:       138ms
  P95:       148ms
  P99:       160ms
```

## How did you test this change?

Ran it across many Next.js benchmark applications.

The entire Next.js test suite passes with this change.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
…sh (facebook#35824)

When flushing the shell, stylesheets with precedence are emitted in the
`<head>` which blocks paint regardless. Outlining a boundary solely
because it has suspensey CSS provides no benefit during the shell flush
and causes a higher-level fallback to be shown unnecessarily (e.g.
"Middle Fallback" instead of "Inner Fallback").

This change passes a flushingInShell flag to hasSuspenseyContent so the
host config can skip stylesheet-only suspensey content when flushing the
shell. Suspensey images (used for ViewTransition animation reveals)
still trigger outlining during the shell since their motivation is
different.

When flushing streamed completions the behavior is unchanged — suspensey
CSS still causes outlining so the parent content can display sooner
while the stylesheet loads.
Cleans up feature flags that do not have an active experiment and which
we don't currently plan to ship, one commit per flag. Notable removals:
* Automatic (inferred) effect dependencies / Fire: abandoned due to
early feedback. Shipped useEffectEvent which addresses some of the
use-cases.
* Inline JSX transform (experimented, not a consistent win)
* Context selectors (experimented, not a sufficient/consistent win given
the benefit the compiler already provides)
* Instruction Reordering (will try a different approach)

To decide which features to remove, I looked at Meta's internal repos as
well as eslint-pugin-react-hooks to see which flags were never
overridden anywhere. That gave a longer list of flags, from which I then
removed some features that I know are used in OSS.
…ok#35839)

When a Suspense boundary suspends during initial mount, the primary
children's fibers are discarded because there is no current tree to
preserve them. If the suspended promise never resolves, the only way to
retry is something external like a context change. However, lazy context
propagation could not find the consumer fibers — they no longer exist in
the tree — so the Suspense boundary was never marked for retry and
remained stuck in fallback state indefinitely.

The fix teaches context propagation to conservatively mark suspended
Suspense boundaries for retry when a parent context changes, even when
the consumer fibers can't be found. This matches the existing
conservative approach used for dehydrated (SSR) Suspense boundaries.
…5827)

Remove dead code left behind after the removal of retryCompileFunction,
enableFire, and inferEffectDependencies:
- Delete ValidateNoUntransformedReferences.ts (always a no-op)
- Remove CompileProgramMetadata type and retryErrors from ProgramContext
- Remove 'client-no-memo' output mode
- Change compileProgram return type from CompileProgramMetadata | null
to void
Add detailed plan for making the React Compiler fault-tolerant by
accumulating errors across all passes instead of stopping at the first
error. This enables reporting multiple compilation errors at once.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35872).
* facebook#35888
* facebook#35884
* facebook#35883
* facebook#35882
* facebook#35881
* facebook#35880
* facebook#35879
* facebook#35878
* facebook#35877
* facebook#35876
* facebook#35875
* facebook#35874
* facebook#35873
* __->__ facebook#35872
…ent (facebook#35873)

Add error accumulation methods to the Environment class:
- #errors field to accumulate CompilerErrors across passes
- recordError() to record a single diagnostic (throws if Invariant)
- recordErrors() to record all diagnostics from a CompilerError
- hasErrors() to check if any errors have been recorded
- aggregateErrors() to retrieve the accumulated CompilerError
- tryRecord() to wrap callbacks and catch CompilerErrors

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35873).
* facebook#35888
* facebook#35884
* facebook#35883
* facebook#35882
* facebook#35881
* facebook#35880
* facebook#35879
* facebook#35878
* facebook#35877
* facebook#35876
* facebook#35875
* facebook#35874
* __->__ facebook#35873
…erance (facebook#35874)

- Change runWithEnvironment/run/compileFn to return
Result<CodegenFunction, CompilerError>
- Wrap all pipeline passes in env.tryRecord() to catch and record
CompilerErrors
- Record inference pass errors via env.recordErrors() instead of
throwing
- Handle codegen Result explicitly, returning Err on failure
- Add final error check: return Err(env.aggregateErrors()) if any errors
accumulated
- Update tryCompileFunction and retryCompileFunction in Program.ts to
handle Result
- Keep lint-only passes using env.logErrors() (non-blocking)
- Update 52 test fixture expectations that now report additional errors

This is the core integration that enables fault tolerance: errors are
caught,
recorded, and the pipeline continues to discover more errors.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35874).
* facebook#35888
* facebook#35884
* facebook#35883
* facebook#35882
* facebook#35881
* facebook#35880
* facebook#35879
* facebook#35878
* facebook#35877
* facebook#35876
* facebook#35875
* __->__ facebook#35874
…rs on env (facebook#35875)

Update 9 validation passes to record errors directly on fn.env instead
of
returning Result<void, CompilerError>:
- validateHooksUsage
- validateNoCapitalizedCalls (also changed throwInvalidReact to
recordError)
- validateUseMemo
- dropManualMemoization
- validateNoRefAccessInRender
- validateNoSetStateInRender
- validateNoImpureFunctionsInRender
- validateNoFreezingKnownMutableFunctions
- validateExhaustiveDependencies

Each pass now calls fn.env.recordErrors() instead of returning
errors.asResult().
Pipeline.ts call sites updated to remove tryRecord() wrappers and
.unwrap().

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35875).
* facebook#35888
* facebook#35884
* facebook#35883
* facebook#35882
* facebook#35881
* facebook#35880
* facebook#35879
* facebook#35878
* facebook#35877
* facebook#35876
* __->__ facebook#35875
… tolerance (facebook#35876)

Update remaining validation passes to record errors on env:
- validateMemoizedEffectDependencies
- validatePreservedManualMemoization
- validateSourceLocations (added env parameter)
- validateContextVariableLValues (changed throwTodo to recordError)
- validateLocalsNotReassignedAfterRender (changed throw to recordError)
- validateNoDerivedComputationsInEffects (changed throw to recordError)

Update inference passes:
- inferMutationAliasingEffects: return void, errors on env
- inferMutationAliasingRanges: return Array<AliasingEffect> directly,
errors on env

Update codegen:
- codegenFunction: return CodegenFunction directly, errors on env
- codegenReactiveFunction: same pattern

Update Pipeline.ts to call all passes directly without tryRecord/unwrap.
Also update AnalyseFunctions.ts which called
inferMutationAliasingRanges.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35876).
* facebook#35888
* facebook#35884
* facebook#35883
* facebook#35882
* facebook#35881
* facebook#35880
* facebook#35879
* facebook#35878
* facebook#35877
* __->__ facebook#35876
…ebook#35877)

Add test fixture demonstrating fault tolerance: the compiler now reports
both a mutation error and a ref access error in the same function, where
previously only one would be reported before bailing out.

Update plan doc to mark all phases as complete.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35877).
* facebook#35888
* facebook#35884
* facebook#35883
* facebook#35882
* facebook#35881
* facebook#35880
* facebook#35879
* facebook#35878
* __->__ facebook#35877
…ing throws (facebook#35881)

Remove `tryRecord()` from the compilation pipeline now that all passes
record
errors directly via `env.recordError()` / `env.recordErrors()`. A single
catch-all try/catch in Program.ts provides the safety net for any pass
that
incorrectly throws instead of recording.

Key changes:
- Remove all ~64 `env.tryRecord()` wrappers in Pipeline.ts
- Delete `tryRecord()` method from Environment.ts
- Add `CompileUnexpectedThrow` logger event so thrown errors are
detectable
- Log `CompileUnexpectedThrow` in Program.ts catch-all for non-invariant
throws
- Fail snap tests on `CompileUnexpectedThrow` to surface pass bugs in
dev
- Convert throwTodo/throwDiagnostic calls in HIRBuilder (fbt, this),
  CodegenReactiveFunction (for-in/for-of), and BuildReactiveFunction to
  record errors or use invariants as appropriate
- Remove try/catch from BuildHIR's lower() since inner throws are now
recorded
- CollectOptionalChainDependencies: return null instead of throwing on
  unsupported optional chain patterns (graceful optimization skip)

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35881).
* facebook#35888
* facebook#35884
* facebook#35883
* facebook#35882
* __->__ facebook#35881
…env.recordError() (facebook#35882)

Removes unnecessary indirection in 17 compiler passes that previously
accumulated errors in a local `CompilerError` instance before flushing
them to `env.recordErrors()` at the end of each pass. Errors are now
emitted directly via `env.recordError()` as they're discovered.

For passes with recursive error-detection patterns
(ValidateNoRefAccessInRender,
ValidateNoSetStateInRender), the internal accumulator is kept but
flushed
via individual `recordError()` calls. For InferMutationAliasingRanges,
a `shouldRecordErrors` flag preserves the conditional suppression logic.
For TransformFire, the throw-based error propagation is replaced with
direct recording plus an early-exit check in Pipeline.ts.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35882).
* facebook#35888
* facebook#35884
* facebook#35883
* __->__ facebook#35882
josephsavona and others added 16 commits February 23, 2026 16:13
…ook#35883)

Rename `state: Environment` to `env: Environment` in
ValidateMemoizedEffectDependencies visitor methods, and
`errorState: Environment` to `env: Environment` in
ValidatePreservedManualMemoization's validateInferredDep.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35883).
* facebook#35888
* facebook#35884
* __->__ facebook#35883
…acebook#35884)

Fix the transformFire early-exit in Pipeline.ts to only trigger on new
errors from transformFire itself, not pre-existing errors from earlier
passes. The previous `env.hasErrors()` check was too broad — it would
early-exit on validation errors that existed before transformFire ran.

Also add missing blank line in CodegenReactiveFunction.ts Context class,
and fix formatting in ValidateMemoizedEffectDependencies.ts.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35884).
* facebook#35888
* __->__ facebook#35884
…doc (facebook#35888)

Add concise fault tolerance documentation to CLAUDE.md and the passes
README covering error accumulation, tryRecord wrapping, and the
distinction between validation vs infrastructure passes. Remove the
detailed planning document now that the work is complete.
## Summary

For apps that use AMD, we need to actually `require()` the
ReactDevToolsBackend and load it from the AMD module cache. This adds a
check for the case where the `ReactDevToolsBackend` isn't defined
globally, and so we load it with `require()`.


## How did you test this change?

Tested through facebook#35886
… different host/port/path (facebook#35886)

## Summary

This enables routing the React Dev Tools through a remote server by
being able to specify host, port, and path for the client to connect to.
Basically allowing the React Dev Tools server to have the client connect
elsewhere.

This setups a `clientOptions` which can be set up through environment
variables when starting the React Dev Tools server.

This change shouldn't affect the traditional usage for React Dev Tools.

EDIT: the additional change was moved to another PR 

## How did you test this change?

Run React DevTools with 
```
$ REACT_DEVTOOLS_CLIENT_HOST=<MY_HOST> REACT_DEVTOOLS_CLIENT_PORT=443 REACT_DEVTOOLS_CLIENT_USE_HTTPS=true REACT_DEVTOOLS_PATH=/__react_devtools__/ yarn start

```

Confirm that my application connects to the local React Dev Tools
server/instance/electron app through my remote server.
…ok#35893)

If a function is known to freeze its inputs, and captures refs, then we
can safely assume those refs are not mutated during render.

An example is React Native's PanResponder, which is designed for use in
interaction handling. Calling `PanResponder.create()` creates an object
that shouldn't be interacted with at render time, so we can treat it as
freezing its arguments, returning a frozen value, and not accessing any
refs in the callbacks passed to it. ValidateNoRefAccessInRender is
updated accordingly - if we see a Freeze <place> and ImmutableCapture
<place> for the same place in the same instruction, we know that it's
not being mutated.

Note that this is a pretty targeted fix. One weakness is that we may not
always emit a Freeze effect if a value is already frozen, which could
cause this optimization not to kick in. The worst case there is that
you'd just get a ref access in render error though, not miscompilation.
And we could always choose to always emit Freeze effects, even for
frozen values, just to retain the information for validations like this.
## 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.
…ok#35912)

## Summary

This fixes the semantics of the `timeStamp` property of events in React
Native.

Currently, most events just assign `Date.now()` (at the time of creating
the event object in JavaScript) as the `timeStamp` property. This is a
divergence with Web and most native platforms, that use a monotonic
timestamp for the value (on Web, the same timestamp provided by
`performance.now()`).

Additionally, many native events specify a timestamp in the event data
object as `timestamp` and gets ignored by the logic in JS as it only
looks at properties named `timeStamp` specifically (camel case).

This PR fixes both issues by:
1. Using `performance.now()` instead of `Date.now()` by default (if
available).
2. Checking for a `timestamp` property before falling back to the
default (apart from `timeStamp`).

## How did you test this change?

Added unit tests for verify the new behavior.
## Summary

This PR updates the event dispatching logic in React Native to expose
the dispatched event in the global scope as done on Web
(https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke) and
in the new implementation of `EventTarget` in React Native
(https://github.com/facebook/react-native/blob/d1b2ddc9cb4f7b4cb795fed197347173ed5c4bfb/packages/react-native/src/private/webapis/dom/events/EventTarget.js#L372).

## How did you test this change?

Added unit tests
…and ValidatePreservedManualMemoization (facebook#35917)

With the recent changes to make the compiler fault tolerant and always
continue through all passes, we can now sometimes report duplicative
errors. Specifically, when `ValidateExhaustiveDependencies` finds
incorrect deps for a useMemo/useCallback call,
`ValidatePreservedManualMemoization` will generally also error for the
same block, producing duplicate errors. The exhaustive deps error is
strictly more informative, so if we've already reported the earlier
error we don't need the later one.

This adds a `hasInvalidDeps` flag to StartMemoize that is set when
ValidateExhaustiveDependencies produces a diagnostic.
ValidatePreservedManualMemoization then skips validation for memo blocks
with this flag set.
Co-authored-by: Dmitrii Troitskii <jsleitor@gmail.com>
…k#35918)

Been enabled in stable for quite a while, also rolled out at Meta.
…n noBackwardRangeExtension heuristic

Track catch handler bindings in AliasingState so the backward-alias
walk in mutate() stops at catch parameters. Without this, catch
parameters aliased to call results in try blocks incorrectly trigger
the effectiveSkipAliases heuristic, preventing backward range
extension and causing 'Expected a break target' crashes when try-catch
blocks get split across reactive scopes.

Fixes 4 compiler crashes (try-catch-try-value-modified-in-catch and
variants) and 1 snapshot regression (try-catch-alias-try-values).
Updates 8 snapshot expectations for tests where the noBackwardRangeExtension
change produces different but functionally correct memoization output.

Closes facebook#35902
@fresh3nough fresh3nough force-pushed the fix/set-state-in-effect-hof-35910 branch from cc3ca6b to 99d6d02 Compare March 1, 2026 22:34
@fresh3nough
Copy link
Owner Author

Superseded by facebook#35934

@fresh3nough fresh3nough closed this Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Compiler Bug]: false negative, compiler skips memoizing a variable that should be memoized