Commit Graph
1024 Commits
Author SHA1 Message Date
Sebastian "Sebbie" SilbermannandGitHub 8d395dce04 [DevTools] Fix alignment of breadcrumbs separator (#35817) 2026-02-18 10:53:49 -08:00
Ruslan LesiutinandGitHub a1077c4596 [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" SilbermannandGitHub 9e1fb1aab8 [DevTools] Dedicated empty state for roots that aren't suspended by anything (#35769) 2026-02-12 17:48:02 +01:00
Sebastian "Sebbie" SilbermannandGitHub f60a406297 [DevTools] Enable Suspense tab by default (#35768) 2026-02-12 16:50:29 +01:00
Ruslan LesiutinandGitHub a3c56f2a29 [DevTools] Remove experimental __IS_INTERNAL_MCP_BUILD__ flag and related code (#35755)
This is unused.
2026-02-11 16:59:43 +00:00
Sebastian "Sebbie" SilbermannandGitHub 6e94f65dfa [DevTools] Display React.optimisticKey in key positions (#35760) 2026-02-11 00:35:36 +01:00
Sebastian "Sebbie" SilbermannandGitHub 2bb178edeb [DevTools] Only block child Suspense boundaries if the parent has all shared suspenders removed (#35737) 2026-02-10 17:52:35 +01:00
d0206107b3 Consistent handling of work tags for rename, delete, and overriding state (#35740)
Co-authored-by: Ruslan Lesiutin <[email protected]>
2026-02-10 16:35:02 +01:00
Sebastian "Sebbie" SilbermannandGitHub 75593946c9 [DevTools] Fix memory leak when unmounting hoistables (#35741) 2026-02-10 13:09:23 +01:00
Sebastian "Sebbie" SilbermannandGitHub c6983ae9a2 [test] Include uniqueSuspenders in Suspense tree snapshots (#35736) 2026-02-10 12:51:58 +01:00
Ruslan LesiutinandGitHub 4210e49bb3 [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:

https://github.com/facebook/react/blob/cba2bc83d8ce7887fabb90775d25b67ade36e5c3/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" SilbermannandGitHub c31165650f [DevTools] Allow renaming Host Component props (#35735) 2026-02-09 23:14:46 +01:00
Sebastian "Sebbie" SilbermannandGitHub e33fa11c51 [DevTools] Fix crash when revealing stable, filtered <Activity> children (#35734) 2026-02-09 22:52:24 +01:00
Ruslan LesiutinandGitHub 4d6a84745e [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" SilbermannandGitHub 048e4a3e1a [Fiber] Avoid duplicate debug info for array children (#35733) 2026-02-09 20:36:56 +01:00
Sebastian "Sebbie" SilbermannandGitHub cba2bc83d8 [test] Move profilingCommitTreeBuilder to versioned renderer (#35711) 2026-02-06 16:00:43 +01:00
Sebastian "Sebbie" SilbermannandGitHub f0425d4279 [DevTools] Fix broken commit tree builder for initial operations (#35710) 2026-02-06 15:16:23 +01:00
Sebastian "Sebbie" SilbermannandGitHub 514d8140c3 [DevTools] Separate breadcrumbs with » (#35705) 2026-02-06 00:40:31 +01:00
Sebastian "Sebbie" SilbermannandGitHub 50a8fab165 [DevTools] Avoid scrollbars in Suspense breadcrumbs (#35700) 2026-02-05 23:27:57 +01:00
Ruslan LesiutinandGitHub bf70c37a31 [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" SilbermannandGitHub ae4dd460f5 [DevTools] Shrink/Deshrink Owners breadcrumbs on any resizes (#35694) 2026-02-05 12:08:02 +01:00
Sebastian "Sebbie" SilbermannandGitHub 6684f5a42e [DevTools] Disable Activity slices by default (#35685) 2026-02-04 10:56:33 +01:00
Ruslan LesiutinandGitHub 2647358088 [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" SilbermannandGitHub f49e0a25cb [DevTools] Apply component filters on initial load (#35587) 2026-01-26 11:06:04 +01:00
Sebastian "Sebbie" SilbermannandGitHub b1d37915c7 [DevTools] Update inspected element on component filter changes (#35599) 2026-01-26 11:04:06 +01:00
Sebastian "Sebbie" SilbermannandGitHub 4337f132b1 [DevTools] Stop setting unused global variables (#35532) 2026-01-16 16:13:29 +01:00
Sebastian "Sebbie" SilbermannandGitHub 0be64314a5 [DevTools] Clear element inspection if host element not owned by any renderer is selected (#35504) 2026-01-16 13:20:44 +01:00
Sebastian "Sebbie" SilbermannandGitHub 919828c090 Typecheck React DevTools extension main script (#35519) 2026-01-16 13:08:28 +01:00
Sebastian "Sebbie" SilbermannandGitHub c33cde8480 [DevTools] Show fallback in inspected element pane when no element is selected (#35503) 2026-01-15 14:28:02 +01:00
Sebastian "Sebbie" SilbermannandGitHub d2df88809e [DevTools] Add React Element pane to browser Elements panel (#35240) 2026-01-15 13:24:06 +01:00
48f97abf95 Improve the detection of changed hooks (#35123)
## Summary

cc @hoxyq 

Fixes https://github.com/facebook/react/issues/28584. Follow up to PR:
https://github.com/facebook/react/pull/34547

This PR updates getChangedHooksIndices to account for the fact that
`useSyncExternalStore`, `useTransition`, `useActionState`,
`useFormState` internally mounts more than one hook while DevTools
should treat it as a single user-facing hook.

Approach idea came from
[this](https://github.com/facebook/react/pull/34547#issuecomment-3504113776)
comment 😄

Before:


https://github.com/user-attachments/assets/6bd5ce80-8b52-4bb8-8bb1-5e91b1e65043


After:


https://github.com/user-attachments/assets/47f56898-ab34-46b6-be7a-a54024dcefee



## How did you test this change?

I used this component to reproduce this issue locally (I followed
instructions in `packages/react-devtools/CONTRIBUTING.md`).

<details><summary>Details</summary>

```ts

import * as React from 'react';

function useDeepNestedHook() {
  React.useState(0); // 1
  return React.useState(1); // 2
}

function useNestedHook() {
  const deepState = useDeepNestedHook();
  React.useState(2); // 3
  React.useState(3); // 4

  return deepState;
}

// Create a simple store for useSyncExternalStore
function createStore(initialValue) {
  let value = initialValue;
  const listeners = new Set();
  return {
    getSnapshot: () => value,
    subscribe: listener => {
      listeners.add(listener);
      return () => {
        listeners.delete(listener);
      };
    },
    update: newValue => {
      value = newValue;
      listeners.forEach(listener => listener());
    },
  };
}

const syncExternalStore = createStore(0);

export default function InspectableElements(): React.Node {
  const [nestedState, setNestedState] = useNestedHook();

  // 5
  const syncExternalValue = React.useSyncExternalStore(
    syncExternalStore.subscribe,
    syncExternalStore.getSnapshot,
  );

  // 6
  const [isPending, startTransition] = React.useTransition();

  // 7
  const [formState, formAction, formPending] = React.useActionState(
    async (prevState, formData) => {
      return {count: (prevState?.count || 0) + 1};
    },
    {count: 0},
  );

  const handleTransition = () => {
    startTransition(() => {
      setState(Math.random());
    });
  };

  // 8
  const [state, setState] = React.useState('test');

  return (
    <>
      <div
        style={{
          padding: '20px',
          display: 'flex',
          flexDirection: 'column',
          gap: '10px',
        }}>
        <div
          onClick={() => setNestedState(Math.random())}
          style={{backgroundColor: 'red', padding: '10px', cursor: 'pointer'}}>
          State: {nestedState}
        </div>

        <button onClick={handleTransition} style={{padding: '10px'}}>
          Trigger Transition {isPending ? '(pending...)' : ''}
        </button>

        <div style={{display: 'flex', gap: '10px', alignItems: 'center'}}>
          <button
            onClick={() => syncExternalStore.update(syncExternalValue + 1)}
            style={{padding: '10px'}}>
            Trigger useSyncExternalStore
          </button>
          <span>Value: {syncExternalValue}</span>
        </div>

        <form
          action={formAction}
          style={{display: 'flex', gap: '10px', alignItems: 'center'}}>
          <button
            type="submit"
            style={{padding: '10px'}}
            disabled={formPending}>
            Trigger useFormState {formPending ? '(pending...)' : ''}
          </button>
          <span>Count: {formState.count}</span>
        </form>

        <div
          onClick={() => setState(Math.random())}
          style={{backgroundColor: 'red', padding: '10px', cursor: 'pointer'}}>
          State: {state}
        </div>
      </div>
    </>
  );
}
```


</details>

---------

Co-authored-by: Ruslan Lesiutin <[email protected]>
2026-01-15 11:06:14 +00:00
Sebastian "Sebbie" SilbermannandGitHub 8d6f60fefe [test] Fix DevTools regression tests (#35501) 2026-01-13 16:00:16 +01:00
Sebastian "Sebbie" SilbermannandGitHub baef9cb1ea [DevTools] Attach async info in filtered fallback to parent of Suspense (#35456) 2026-01-10 11:33:48 +01:00
laurenandGitHub c991e8353d [ci] Increase DevTools test shards and bump timeout (#35459)
[ci] Increase DevTools test shards and bump timeout

- Increase DevTools test shards from 3 to 5
- Bump timeout to 20s

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35459).
* #35458
* __->__ #35459
2026-01-06 21:23:05 -05:00
7cdeeb16eb Devtools disable log dimming strict mode setting (#35207)
<!--

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`).

-->

## Summary

Currently, every second console log is dimmed, receiving a special style
that indicates to user that it was raising because of [React Strict
Mode](https://react.dev/reference/react/StrictMode) second rendering.
This introduces a setting to disable this.

## How did you test this change?
Test in console-test.js


https://github.com/user-attachments/assets/af6663ac-f79b-4824-95c0-d46b0c8dec12

Browser extension react devtools


https://github.com/user-attachments/assets/7e2ecb7a-fbdf-4c72-ab45-7e3a1c6e5e44

React native dev tools:


https://github.com/user-attachments/assets/d875b3ac-1f27-43f8-8d9d-12b2d65fa6e6

---------

Co-authored-by: Ruslan Lesiutin <[email protected]>
2025-12-15 13:41:43 +00:00
Ruslan LesiutinandGitHub eea0071c3a fix[devtools]: feature-check document with typeof instead of direct reference (#35343)
Follow-up to https://github.com/facebook/react/pull/35296.

We can get `ReferenceError` if this is unavailable. Using `typeof` check
instead for safety.
2025-12-11 12:15:00 +00:00
Ruslan LesiutinandGitHub e9f545c3c2 fix[devtools]: feature-check structure stack trace methods (#35293)
`Error.prepareStackTrace` is non-standard feature and not all JavaScript
runtimes implement the methods that we are using in React DevTools
backend.

This PR adds additional checks for the presence of the methods that we
are using.
2025-12-10 19:21:54 +00:00
Ruslan LesiutinandGitHub 6a426a5011 fix[devtools]: still show overlay, if getClientRects is not implemented (#35294)
Follow-up to https://github.com/facebook/react/pull/34653.

React Native doesn't implement `getClientRect`, since this is applicable
to CSS box, which is not a concept for Native (maybe yet).

I am loosening the condition that gates `showOverlay()` call to pass if
`getClientRect` is not implemented.

Conceptually, everything that is inside `react-devtools-shared/backend`
should be Host-agnostic, because both on Web and Native it is installed
inside the Host JavaScript runtime, be it main frame of the page, or RN
instance. Since overlay & highlighting logic also lives there, it should
also follow these principles.
2025-12-10 19:21:28 +00:00
emily8rownandGitHub 6f7f9d4d1c [Devtools] Navigating commits performance panel hotkey (#35238)
## Summary
Add keyboard shortcuts (Cmd/Ctrl + Left/Right arrow keys) to navigate
between commits in the Profiler's snapshot view.

Moved `filteredCommitIndices` management and commit navigation logic
(`selectNextCommitIndex`, `selectPrevCommitIndex`) from
`SnapshotSelector` into `useCommitFilteringAndNavigation` used by
`ProfilerContext` to enable keyboard shortcuts from the top-level
Profiler component.

## How did you test this change?
- New tests in ProfilerContext-tests
- Built browser extension: `yarn build:<browser name>`
- tested in browser: `yarn run test:<browser name>`
- Manually verified Left/Right arrow navigation cycles through commits
- Verified navigation respects commit duration filter
- Verified reload-and-profile button unaffected

Chrome:


https://github.com/user-attachments/assets/01d2a749-13dc-4d08-8bcb-3d4d45a5f97c

Edge with duration filter:


https://github.com/user-attachments/assets/a7f76ff7-2a0b-4b9c-a0ce-d4449373308b

firefox mixing hotkey with clicking arrow buttons:


https://github.com/user-attachments/assets/48912d68-7c75-40f2-a203-5e6d7e6b2d99
2025-12-10 13:07:35 +00:00
Ruslan LesiutinandGitHub c27682c6cc fix[devtools]: no-op unsupported backend bridge events (#35296)
Follow-up to https://github.com/facebook/react/pull/34641.
Similar to https://github.com/facebook/react/pull/35293,
https://github.com/facebook/react/pull/35294.

React DevTools backend can be used in non-DOM environments, so we have
to feature-check some DOM APIs.
For now I am just no-oping newly added commands for Native, we should
revisit this decision once we would roll out Suspense panel there, if
needed. I am not sure if scrolling will be required as much as it is
needed on Web.

`isReactNativeEnvironment()` check is kinda clowny, but we've been
relying on it for quite some time already.
2025-12-05 16:41:58 +00:00
Sebastian "Sebbie" SilbermannandGitHub 37482da5f8 [test] Only run tests overriding fallback and error states in supported versions (#35234) 2025-11-27 16:15:14 +01:00
Sebastian "Sebbie" SilbermannandGitHub e3736f803e [DevTools] Move "Back to full tree view" into Activity list item (#35164) 2025-11-26 10:42:18 +01:00
acb5174375 [DevTools] hotkey to start/stop profiling (#35160)
## Summary

The built-in browser profiler supports starting/stopping with Cmd+E. For
Symmetry this adds the same hotkey for react devtools profiler.

## How did you test this change?
yarn build:\<browser name\> 
yarn run test:\<browser name\>

<img width="483" height="135" alt="Screenshot 2025-11-17 at 14 30 34"
src="https://github.com/user-attachments/assets/426939aa-15da-4c21-87a4-e949e6949482"
/>

firefox:

https://github.com/user-attachments/assets/6f225b90-828f-4e79-a364-59d6bc942f83

edge:

https://github.com/user-attachments/assets/5b2e9242-f0e8-481b-99a2-2dd78099f3ac

chrome:

https://github.com/user-attachments/assets/790aab02-2867-4499-aec1-e32e38c763f9

---------

Co-authored-by: Ruslan Lesiutin <[email protected]>
2025-11-21 11:37:10 -05:00
Sebastian MarkbågeandGitHub 57cc05e2b6 Add expertimental optimisticKey behind a flag (#35162)
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.
2025-11-18 16:29:18 -05:00
Sebastian "Sebbie" SilbermannandGitHub 14b15045f8 [DevTools] Name root "Transition" when focusing on Activity (#35108) 2025-11-18 10:16:58 +01:00
Sebastian "Sebbie" SilbermannandGitHub 2cf1428a9b [DevTools] Show list of named Activities in Suspense tab (#35092) 2025-11-18 09:52:44 +01:00
Sebastian "Sebbie" SilbermannandGitHub 492da95553 [DevTools] Batch updates when updating component filters (#35093) 2025-11-11 23:20:22 +01:00
Sebastian "Sebbie" SilbermannandGitHub ff13b893cf [DevTools] Send root unmount as a regular removal operation (#35107) 2025-11-11 23:08:54 +01:00
Sebastian "Sebbie" SilbermannandGitHub 0d6ecc9276 [DevTools] Measure when reconnecting Suspense (#35098) 2025-11-10 20:55:31 +01:00
Sebastian "Sebbie" SilbermannandGitHub 2281803750 [DevTools] Don't attempt to draw bounding box if inspected element is not a Suspense (#35097) 2025-11-10 20:01:59 +01:00