[DevTools] Enable Suspense tab by default (#35768)
Sebastian "Sebbie" Silbermann committed
Feb 12, 2026 at 16:50 UTC
705055d7ac3da03927758b22d8aea4b2e5913961
5 files changed
+12
-59
packages/react-devtools-extensions/src/main/index.js
+1
-6
@@ -167,10 +167,6 @@ function createBridgeAndStore() {
167
supportsClickToInspect: true,
168
});
169
170
- store.addListener('enableSuspenseTab', () => {
171
- createSuspensePanel();
172
- });
173
-
170
store.addListener('settingsUpdated', (hookSettings, componentFilters) => {
171
chrome.storage.local.set({...hookSettings, componentFilters});
172
});
@@ -565,8 +561,7 @@ function mountReactDevTools() {
561
createProfilerPanel();
562
createSourcesEditorPanel();
563
createElementsInspectPanel();
568
- // Suspense Tab is created via the hook
569
- // TODO(enableSuspenseTab): Create eagerly once Suspense tab is stable
564
+ createSuspensePanel();
565
}
566
567
let reactPollingInstance = null;
packages/react-devtools-shared/src/backend/agent.js
+1
-11
@@ -38,7 +38,7 @@ import type {
38
ElementType,
39
} from 'react-devtools-shared/src/frontend/types';
40
import type {GroupItem} from './views/TraceUpdates/canvas';
41
-import {gte, isReactNativeEnvironment} from './utils';
41
+import {isReactNativeEnvironment} from './utils';
42
import {
43
sessionStorageGetItem,
44
sessionStorageRemoveItem,
@@ -961,16 +961,6 @@ export default class Agent extends EventEmitter<{
961
962
rendererInterface.setTraceUpdatesEnabled(this._traceUpdatesEnabled);
963
964
- const renderer = rendererInterface.renderer;
965
- if (renderer !== null) {
966
- const devRenderer = renderer.bundleType === 1;
967
- const enableSuspenseTab =
968
- devRenderer && gte(renderer.version, '19.3.0-canary');
969
- if (enableSuspenseTab) {
970
- this._bridge.send('enableSuspenseTab');
971
- }
972
- }
973
-
964
// When the renderer is attached, we need to tell it whether
965
// we remember the previous selection that we'd like to restore.
966
// It'll start tracking mounts for matches to the last selection path.
packages/react-devtools-shared/src/bridge.js
-1
@@ -199,7 +199,6 @@ export type BackendEvents = {
199
backendInitialized: [],
200
backendVersion: [string],
201
bridgeProtocol: [BridgeProtocol],
202
- enableSuspenseTab: [],
202
extensionBackendInitialized: [],
203
fastRefreshScheduled: [],
204
getSavedPreferences: [],
packages/react-devtools-shared/src/devtools/store.js
-13
@@ -143,7 +143,6 @@ export default class Store extends EventEmitter<{
143
backendVersion: [],
144
collapseNodesByDefault: [],
145
componentFilters: [],
146
- enableSuspenseTab: [],
146
error: [Error],
147
hookSettings: [$ReadOnly<DevToolsHookSettings>],
148
hostInstanceSelected: [Element['id'] | null],
@@ -239,8 +238,6 @@ export default class Store extends EventEmitter<{
238
_supportsClickToInspect: boolean = false;
239
_supportsTimeline: boolean = false;
240
_supportsTraceUpdates: boolean = false;
242
- // Dynamically set if the renderer supports the Suspense tab.
243
- _supportsSuspenseTab: boolean = false;
241
242
_isReloadAndProfileFrontendSupported: boolean = false;
243
_isReloadAndProfileBackendSupported: boolean = false;
@@ -341,7 +338,6 @@ export default class Store extends EventEmitter<{
338
bridge.addListener('hookSettings', this.onHookSettings);
339
bridge.addListener('backendInitialized', this.onBackendInitialized);
340
bridge.addListener('selectElement', this.onHostInstanceSelected);
344
- bridge.addListener('enableSuspenseTab', this.onEnableSuspenseTab);
341
}
342
343
// This is only used in tests to avoid memory leaks.
@@ -2394,15 +2390,6 @@ export default class Store extends EventEmitter<{
2390
}
2391
}
2392
2397
- get supportsSuspenseTab(): boolean {
2398
- return this._supportsSuspenseTab;
2399
- }
2400
-
2401
- onEnableSuspenseTab = (): void => {
2402
- this._supportsSuspenseTab = true;
2403
- this.emit('enableSuspenseTab');
2404
- };
2405
-
2393
// The Store should never throw an Error without also emitting an event.
2394
// Otherwise Store errors will be invisible to users,
2395
// but the downstream errors they cause will be reported as bugs.
packages/react-devtools-shared/src/devtools/views/DevTools.js
+10
-28
@@ -135,21 +135,7 @@ const suspenseTab = {
135
title: 'React Suspense',
136
};
137
138
-const defaultTabs = [componentsTab, profilerTab];
139
-const tabsWithSuspense = [componentsTab, profilerTab, suspenseTab];
140
-
141
-function useIsSuspenseTabEnabled(store: Store): boolean {
142
- const subscribe = useCallback(
143
- (onStoreChange: () => void) => {
144
- store.addListener('enableSuspenseTab', onStoreChange);
145
- return () => {
146
- store.removeListener('enableSuspenseTab', onStoreChange);
147
- };
148
- },
149
- [store],
150
- );
151
- return React.useSyncExternalStore(subscribe, () => store.supportsSuspenseTab);
152
-}
138
+const tabs = [componentsTab, profilerTab, suspenseTab];
139
140
export default function DevTools({
141
bridge,
@@ -183,8 +169,6 @@ export default function DevTools({
169
LOCAL_STORAGE_DEFAULT_TAB_KEY,
170
defaultTab,
171
);
186
- const enableSuspenseTab = useIsSuspenseTabEnabled(store);
187
- const tabs = enableSuspenseTab ? tabsWithSuspense : defaultTabs;
172
173
let tab = currentTab;
174
@@ -364,17 +348,15 @@ export default function DevTools({
348
}
349
/>
350
</div>
367
- {enableSuspenseTab && (
368
- <div
369
- className={styles.TabContent}
370
- hidden={tab !== 'suspense'}>
371
- <SuspenseTab
372
- portalContainer={
373
- suspensePortalContainer
374
- }
375
- />
376
- </div>
377
- )}
351
+ <div
352
+ className={styles.TabContent}
353
+ hidden={tab !== 'suspense'}>
354
+ <SuspenseTab
355
+ portalContainer={
356
+ suspensePortalContainer
357
+ }
358
+ />
359
+ </div>
360
</div>
361
{editorPortalContainer ? (
362
<EditorPane