Commit Graph

7121 Commits

Author SHA1 Message Date
Hendrik Liebau
47d1ad1454 [Flight] Skip transferReferencedDebugInfo during debug info resolution (#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.
2026-02-16 09:22:32 -08:00
Azat S.
e8c6362678 [eslint-plugin-react-hooks] Add ESLint v10 support (#35720)
## 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.
2026-02-13 10:26:01 -08:00
Ruslan Lesiutin
03ca38e6e7 [DevTools] Check suspense child node presence in parentSuspense.children before removing (#35775)
Currently, this silently removes the last child in the list, which
doesn't contain the `id`.
2026-02-13 15:33:22 +00:00
Sebastian "Sebbie" Silbermann
6066c782fe [DevTools] Dedicated empty state for roots that aren't suspended by anything (#35769) 2026-02-12 17:48:02 +01:00
Sebastian "Sebbie" Silbermann
705055d7ac [DevTools] Enable Suspense tab by default (#35768) 2026-02-12 16:50:29 +01:00
Ruslan Lesiutin
8374c2abf1 [DevTools] Remove experimental __IS_INTERNAL_MCP_BUILD__ flag and related code (#35755)
This is unused.
2026-02-11 16:59:43 +00:00
Ricky
892c68605c [fiber] bugfix - don't show <Offscreen> in error message. (#35763)
## 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.
2026-02-11 11:20:51 -05:00
Jack Pope
cd515d7e22 Minor DOM FragmentInstance refactors (#35641)
Handles TODOs, small follow up refactors
2026-02-11 10:03:36 -05:00
Jack Pope
78f5c504b7 Notify FragmentInstance of added/removed text (#35637)
Follow up to https://github.com/facebook/react/pull/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.
2026-02-11 09:26:22 -05:00
Sebastian "Sebbie" Silbermann
e49335e961 [DevTools] Display React.optimisticKey in key positions (#35760) 2026-02-11 00:35:36 +01:00
Sebastian "Sebbie" Silbermann
57b79b0388 [DevTools] Only block child Suspense boundaries if the parent has all shared suspenders removed (#35737) 2026-02-10 17:52:35 +01:00
Sebastian "Sebbie" Silbermann
70890e7c58 Consistent handling of work tags for rename, delete, and overriding state (#35740)
Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
2026-02-10 16:35:02 +01:00
Sebastian "Sebbie" Silbermann
f23aa1d9f5 [DevTools] Fix memory leak when unmounting hoistables (#35741) 2026-02-10 13:09:23 +01:00
Sebastian "Sebbie" Silbermann
49c3b270f9 [test] Include uniqueSuspenders in Suspense tree snapshots (#35736) 2026-02-10 12:51:58 +01:00
Ruslan Lesiutin
c6bb26bf83 [DevTools] Don't capture durations for disconnected subtrees when profiling (#35718)
After https://github.com/facebook/react/pull/34089, when updating
(possibly, mounting) inside disconnected subtree, we don't record this
as an operation. This only happens during reconnect. The issue is that
`recordProfilingDurations()` can be called, which diffs tree base
duration and reports it to the Frontend:

65db1000b9/packages/react-devtools-shared/src/backend/fiber/renderer.js (L4506-L4521)

This operation can be recorded before the "Add" operation, and it will
not be resolved properly on the Frontend side.

Before the fix:
```
commit tree › Suspense › should handle transitioning from fallback back to content during profiling

    Could not clone the node: commit tree does not contain fiber "5". This is a bug in React DevTools.

      162 |     const existingNode = nodes.get(id);
      163 |     if (existingNode == null) {
    > 164 |       throw new Error(
          |             ^
      165 |         `Could not clone the node: commit tree does not contain fiber "${id}". This is a bug in React DevTools.`,
      166 |       );
      167 |     }

      at getClonedNode (packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js:164:13)
      at updateTree (packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js:348:24)
      at getCommitTree (packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js:112:20)
      at ProfilingCache.getCommitTree (packages/react-devtools-shared/src/devtools/ProfilingCache.js:40:46)
      at Object.<anonymous> (packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js:257:44)
```
2026-02-09 22:18:17 +00:00
Sebastian "Sebbie" Silbermann
6a939d0b54 [DevTools] Allow renaming Host Component props (#35735) 2026-02-09 23:14:46 +01:00
Sebastian "Sebbie" Silbermann
4c9d62d2b4 [DevTools] Fix crash when revealing stable, filtered <Activity> children (#35734) 2026-02-09 22:52:24 +01:00
Ruslan Lesiutin
24f215ce8b [DevTools] Fix false-positive re-render reports for filtered nodes (#35723)
Fixes https://github.com/facebook/react/issues/33423,
https://github.com/facebook/react/issues/35245,
https://github.com/facebook/react/issues/19732.

As demoed
[here](https://github.com/facebook/react/issues/33423#issuecomment-2970750588),
React DevTools incorrectly highlights re-renders for descendants of
filtered-out nodes that didn't actually render.

There were multiple fixes suggesting changes in `didFiberRender()`
function, but these doesn't seem right, because this function is used in
a context of whether the Fiber actually rendered something (updated),
not re-rendered compared to the previous Fiber.

Instead, this PR adds additional validation at callsites that either
used for highlighting re-renders or capturing tree base durations and
are relying on `didFiberRender`. I've also added a few tests that
reproduce the failure scenario. Without the changes, the tests are
failing.
2026-02-09 20:39:33 +00:00
Sebastian "Sebbie" Silbermann
eab523e2a9 [Fiber] Avoid duplicate debug info for array children (#35733) 2026-02-09 20:36:56 +01:00
Hendrik Liebau
272441a9ad [Flight] Add unstable_allowPartialStream option to Flight Client (#35731)
When using a partial prerender stream, i.e. a prerender that is
intentionally aborted before all I/O has resolved, consumers of
`createFromReadableStream` would need to keep the stream unclosed to
prevent React Flight from erroring on unresolved chunks. However, some
browsers (e.g. Chrome, Firefox) keep unclosed ReadableStreams with
pending reads as native GC roots, retaining the entire Flight response.

With this PR we're adding an `unstable_allowPartialStream` option, that
allows consumers to close the stream normally. The Flight Client's
`close()` function then transitions pending chunks to halted instead of
erroring them. Halted chunks keep Suspense fallbacks showing (i.e. they
never resolve), and their `.then()` is a no-op so no new listeners
accumulate. Inner stream chunks (ReadableStream/AsyncIterable) are
closed gracefully, and `getChunk()` returns halted chunks for new IDs
that are accessed after closing the response. Blocked chunks are left
alone because they may be waiting on client-side async operations like
module loading, or on forward references to chunks that appeared later
in the stream, both of which resolve independently of closing.
2026-02-09 19:19:32 +01:00
Hendrik Liebau
b07aa7d643 [Flight] Fix encodeReply for JSX with temporary references (#35730)
`encodeReply` throws "React Element cannot be passed to Server Functions
from the Client without a temporary reference set" when a React element
is the root value of a `serializeModel` call (either passed directly or
resolved from a promise), even when a temporary reference set is
provided.

The cause is that `resolveToJSON` hits the `REACT_ELEMENT_TYPE` switch
case before reaching the `existingReference`/`modelRoot` check that
regular objects benefit from. The synthetic JSON root created by
`JSON.stringify` is never tracked in `writtenObjects`, so
`parentReference` is `undefined` and the code falls through to the
throw. This adds a `modelRoot` check in the `REACT_ELEMENT_TYPE` case,
following the same pattern used for promises and plain objects.

The added `JSX as root model` test also uncovered a pre-existing crash
in the Flight Client: when the JSX element round-trips back, it arrives
as a frozen object (client-created elements are frozen in DEV), and
`Object.defineProperty` for `_debugInfo` fails because frozen objects
are non-configurable. The same crash can occur with JSX exported as a
client reference. For now, we're adding `!Object.isFrozen()` guards in
`moveDebugInfoFromChunkToInnerValue` and `addAsyncInfo` to prevent the
crash, which means debug info is silently dropped for frozen elements.
The proper fix would likely be to clone the element so each rendering
context gets its own mutable copy with correct debug info.

closes #34984
closes #35690
2026-02-09 16:17:53 +01:00
Jimmy Lai
2dd9b7cf76 [Flight] Fix debug channel flag in Node.js server renderToPipeableStream (#35724)
## Summary

- Fixes the `createRequest` call in `renderToPipeableStream` to pass
`debugChannelReadable !== undefined` instead of `debugChannel !==
undefined` in the turbopack, esm, and parcel Node.js server
implementations
- The webpack version already had the correct check; this brings the
other bundler implementations in line

The bug was introduced in #33754. With `debugChannel !== undefined`, the
server could signal that debug info should be emitted even when only a
write-only debug channel is provided (no readable side), potentially
causing the client to block forever waiting for debug data that never
arrives.
2026-02-08 20:14:15 +01:00
Sebastian "Sebbie" Silbermann
65db1000b9 [test] Move profilingCommitTreeBuilder to versioned renderer (#35711) 2026-02-06 16:00:43 +01:00
Sebastian "Sebbie" Silbermann
2a879cdc95 [DevTools] Fix broken commit tree builder for initial operations (#35710) 2026-02-06 15:16:23 +01:00
Sebastian "Sebbie" Silbermann
9a5996a6c1 [flags] Cleanup enableHalt (#35708) 2026-02-06 10:33:51 +01:00
Sebastian "Sebbie" Silbermann
1c66ac740c [DevTools] Separate breadcrumbs with » (#35705) 2026-02-06 00:40:31 +01:00
Sebastian "Sebbie" Silbermann
8b276df415 [DevTools] Avoid scrollbars in Suspense breadcrumbs (#35700) 2026-02-05 23:27:57 +01:00
Ricky
95ffd6cd9c Disable parallel transitions in canary (#35709)
Accidentally enabled this
2026-02-05 13:34:23 -05:00
Ruslan Lesiutin
b9323509be [DevTools] Throw an error when attempting to clone non-existent node (#35702)
There is an existing issue with serialisation logic for the traces from
Profiler panel.

I've discovered that `TREE_OPERATION_UPDATE_TREE_BASE_DURATION`
operation for some reason appears earlier in a sequence of operations,
before the `TREE_OPERATION_ADD` that registers the new node. It ends up
cloning non-existent node, which just creates an empty object and adds
it to the map of nodes.

This change only adds additional layer of validation to cloning logic,
so we don't swallow the error, if we attempt to clone non-existent node.
2026-02-05 17:49:18 +00:00
Sebastian "Sebbie" Silbermann
bb53387716 [DevTools] Shrink/Deshrink Owners breadcrumbs on any resizes (#35694) 2026-02-05 12:08:02 +01:00
Ricky
3aaab92a26 [flags] add enableEffectEventMutationPhase (#35548)
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.
2026-02-04 15:04:57 -05:00
Ricky
087a34696f [test] add activity test with gSBU and enableViewTransition bugfix (#35555)
Related to https://github.com/facebook/react/pull/35548,
`enableViewTransition` fixes a bug where `getSnapshotBeforeUpdate` was
running in hidden trees when it shouldn't (`componentWillUpdate` won't
run for hidden updates, and when it becomes visible it will be called
with `componentWillMount`).
2026-02-04 14:37:45 -05:00
Ricky
6913ea4d28 [flags] Add enableParallelTransitions (#35392)
## 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.
2026-02-04 13:58:34 -05:00
Hendrik Liebau
cf993fb457 [Flight] Fix stack overflow in visitAsyncNode with deep async chains (#35612)
Database libraries like Gel/EdgeDB can create very long linear chains of
async sequences through temporal async sequencing in connection pools.
The recursive traversal of `node.previous` chains in `visitAsyncNode`
causes stack overflow on these deep chains.

The fix converts the `previous` chain traversal from recursive to
iterative. We collect the chain into an array, then process from deepest
to shallowest.

The `awaited` traversal remains recursive since its depth is bounded by
promise dependency depth, not by the number of event loop turns. Each
`awaited` branch still benefits from the iterative `previous` handling
within its own traversal.

I've verified that this fixes the
[repro](https://github.com/jere-co/next-debug) provided in #35246.

closes #35246
2026-02-04 19:43:23 +01:00
Jorge Cabiedes
c137dd6f54 Fix exhaustive deps bug with flow type casting. (#35691)
Summary:
I noticed there's a bug where the lint will recognize the type on a cast
annotation as a missing dependency;

```
        function MyComponent() {
          type ColumnKey = 'id' | 'name';
          type Item = {id: string, name: string};

          const columns = useMemo(
            () => [
              {
                type: 'text',
                key: 'id',
              } as TextColumn<ColumnKey, Item>,
                              ^^^^^^^^ here
            ],
            [],
          );
        }
```

This is due to the AST of AsExpressions being something like:

AsExpression
  └── typeAnnotation: GenericTypeAnnotation
        └── typeParameters: TypeParameterInstantiation
              └── params[0]: GenericTypeAnnotation
                    └── id: Identifier (name: "ColumnKey")

Where `ColumnKey` never has a TypeParameter Annotation. So we need to
consider it to be a flow type due to it belonging to a
GenericTypeAnnotation

Test Plan:
Added unit tests

Before:
```
Test Suites: 1 failed, 2 passed, 3 total
Tests:       2 failed, 5065 passed, 5067 total
Snapshots:   0 total
Time:        16.517 s
Ran all test suites.
error Command failed with exit code 1.
```

After:
```
 PASS  __tests__/ReactCompilerRuleTypescript-test.ts
 PASS  __tests__/ESLintRulesOfHooks-test.js (6.192 s)
 PASS  __tests__/ESLintRuleExhaustiveDeps-test.js (9.97 s)

Test Suites: 3 passed, 3 total
Tests:       5067 passed, 5067 total
Snapshots:   0 total
Time:        10.21 s, estimated 11 s
Ran all test suites.
  Done in 12.66s.
```
2026-02-04 08:24:24 -08:00
Sebastian "Sebbie" Silbermann
f84ce5a45c [flags] Turn on enableViewTransition in OSS react-test-renderer (#35684) 2026-02-04 11:42:49 +01:00
Sebastian "Sebbie" Silbermann
c9ff56ec74 [DevTools] Disable Activity slices by default (#35685) 2026-02-04 10:56:33 +01:00
Sebastian "Sebbie" Silbermann
6853d7ab2f [Perf Tracks] Prevent crash when accessing $$typeof (#35679) 2026-02-03 17:53:45 +01:00
Sebastian "Sebbie" Silbermann
e32c126121 [flags] Turn on enableAsyncDebugInfo everywhere (#35683) 2026-02-03 17:52:57 +01:00
Ricky
3e00319b35 [Flight] allow context providers from client modules (#35675)
Allows Server Components to import Context from a `"use client'` module
and render its Provider.

Only tricky part was that I needed to add `REACT_CONTEXT_TYPE` handling
in mountLazyComponent so lazy-resolved Context types can be rendered.
Previously only functions, REACT_FORWARD_REF_TYPE, and REACT_MEMO_TYPE
were handled.

Tested in the Flight fixture.

ty bb claude

Closes https://github.com/facebook/react/issues/35340

---------

Co-authored-by: Sophie Alpert <git@sophiebits.com>
2026-02-03 10:22:57 -05:00
Sebastian "Sebbie" Silbermann
3419420e8b [flags] Cleanup enableActivity (#35681) 2026-02-03 16:08:18 +01:00
Janka Uryga
b1533b034e [Flight] Allow overriding request.timeOrigin via options.startTime (#35598)
Currently, IO that finished before the request started is not considered
IO:

6a0ab4d2dd/packages/react-server/src/ReactFlightServer.js (L5338-L5343)
This leads to loss of debug info when a flight stream is deserialized
and serialized again.
We can solve this by allowing "when the the request started" to be set
to a point in the past, when the original stream started by doing

```js
const startTime = performance.now() + performance.timeOrigin
// ... stuff happens and time passes...
ReactServer.renderToReadableStream(..., { startTime })
```
2026-02-03 15:29:51 +01:00
Ruslan Lesiutin
5dad2b47b8 [DevTools] Fix commit index reset when switching profiler roots (#35672)
Fixes https://github.com/facebook/react/issues/31463,
https://github.com/facebook/react/issues/30114.

When switching between roots in the profiler flamegraph, the commit
index was preserved from the previous root. This caused an error
"Invalid commit X. There are only Y commits." when the new root had
fewer commits than the selected index.

This fix resets the commit index to 0 (or null if no commits) when the
commitData changes, which happens when switching roots.
2026-02-03 12:44:04 +00:00
Sebastian "Sebbie" Silbermann
7b023d7073 [react-dom] Include submitter in submit events (#35590) 2026-02-02 21:17:31 +01:00
Sebastian "Sebbie" Silbermann
dcab44d757 [react-dom] Fire onReset when automatically resetting forms (#35176) 2026-02-02 21:17:14 +01:00
Sebastian "Sebbie" Silbermann
ed4bd540ca [Flight] Warn once if eval is disabled in dev environment (#35661) 2026-02-02 12:56:14 +01:00
Sebastian "Sebbie" Silbermann
da64117876 [Perf Tracks] Handle function names that aren't strings (#35659) 2026-01-29 18:32:18 +01:00
Ricky
230772f99d [tests] Fix ReactDOMAttribute-test (#35654)
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`.
2026-01-28 16:00:40 -05:00
Jack Pope
875b06489f Add text node support to FragmentInstance operations (#35630)
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`
2026-01-28 14:45:17 -05:00
Jan Olaf Martin
d4d099f05b [flags] make enableTrustedTypesIntegration dynamic (#35646)
Co-authored-by: Rick Hanlon <rickhanlonii@meta.com>
2026-01-28 13:15:33 -05:00