diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js
index 92e7fc6586111..93c7048b2bc5b 100644
--- a/packages/react-devtools-shared/src/__tests__/store-test.js
+++ b/packages/react-devtools-shared/src/__tests__/store-test.js
@@ -2148,8 +2148,8 @@ describe('Store', () => {
act(() => render());
});
expect(store).toMatchInlineSnapshot(`[root]`);
- expect(store.errorCount).toBe(0);
- expect(store.warningCount).toBe(0);
+ expect(store.componentWithErrorCount).toBe(0);
+ expect(store.componentWithWarningCount).toBe(0);
});
// Regression test for https://github.com/facebook/react/issues/23202
diff --git a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
index 432fb1771b6ff..26cd51383297f 100644
--- a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
+++ b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
@@ -420,8 +420,8 @@ describe('Store component filters', () => {
});
expect(store).toMatchInlineSnapshot(``);
- expect(store.errorCount).toBe(0);
- expect(store.warningCount).toBe(0);
+ expect(store.componentWithErrorCount).toBe(0);
+ expect(store.componentWithWarningCount).toBe(0);
await actAsync(async () => (store.componentFilters = []));
expect(store).toMatchInlineSnapshot(`
@@ -460,8 +460,8 @@ describe('Store component filters', () => {
]),
);
expect(store).toMatchInlineSnapshot(`[root]`);
- expect(store.errorCount).toBe(0);
- expect(store.warningCount).toBe(0);
+ expect(store.componentWithErrorCount).toBe(0);
+ expect(store.componentWithWarningCount).toBe(0);
await actAsync(async () => (store.componentFilters = []));
expect(store).toMatchInlineSnapshot(`
@@ -510,8 +510,8 @@ describe('Store component filters', () => {
});
expect(store).toMatchInlineSnapshot(``);
- expect(store.errorCount).toBe(0);
- expect(store.warningCount).toBe(0);
+ expect(store.componentWithErrorCount).toBe(0);
+ expect(store.componentWithWarningCount).toBe(0);
await actAsync(async () => (store.componentFilters = []));
expect(store).toMatchInlineSnapshot(`
@@ -550,8 +550,8 @@ describe('Store component filters', () => {
]),
);
expect(store).toMatchInlineSnapshot(`[root]`);
- expect(store.errorCount).toBe(0);
- expect(store.warningCount).toBe(0);
+ expect(store.componentWithErrorCount).toBe(0);
+ expect(store.componentWithWarningCount).toBe(0);
await actAsync(async () => (store.componentFilters = []));
expect(store).toMatchInlineSnapshot(`
diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js
index b54907338b372..b1544126d8d6e 100644
--- a/packages/react-devtools-shared/src/devtools/store.js
+++ b/packages/react-devtools-shared/src/devtools/store.js
@@ -114,8 +114,8 @@ export default class Store extends EventEmitter<{
_bridge: FrontendBridge;
// Computed whenever _errorsAndWarnings Map changes.
- _cachedErrorCount: number = 0;
- _cachedWarningCount: number = 0;
+ _cachedComponentWithErrorCount: number = 0;
+ _cachedComponentWithWarningCount: number = 0;
_cachedErrorAndWarningTuples: ErrorAndWarningTuples | null = null;
// Should new nodes be collapsed by default when added to the tree?
@@ -196,6 +196,7 @@ export default class Store extends EventEmitter<{
_shouldCheckBridgeProtocolCompatibility: boolean = false;
_hookSettings: $ReadOnly | null = null;
+ _shouldShowWarningsAndErrors: boolean = false;
constructor(bridge: FrontendBridge, config?: Config) {
super();
@@ -383,8 +384,24 @@ export default class Store extends EventEmitter<{
return this._bridgeProtocol;
}
- get errorCount(): number {
- return this._cachedErrorCount;
+ get componentWithErrorCount(): number {
+ if (!this._shouldShowWarningsAndErrors) {
+ return 0;
+ }
+
+ return this._cachedComponentWithErrorCount;
+ }
+
+ get componentWithWarningCount(): number {
+ if (!this._shouldShowWarningsAndErrors) {
+ return 0;
+ }
+
+ return this._cachedComponentWithWarningCount;
+ }
+
+ get displayingErrorsAndWarningsEnabled(): boolean {
+ return this._shouldShowWarningsAndErrors;
}
get hasOwnerMetadata(): boolean {
@@ -480,10 +497,6 @@ export default class Store extends EventEmitter<{
return this._unsupportedRendererVersionDetected;
}
- get warningCount(): number {
- return this._cachedWarningCount;
- }
-
containsElement(id: number): boolean {
return this._idToElement.has(id);
}
@@ -581,7 +594,11 @@ export default class Store extends EventEmitter<{
}
// Returns a tuple of [id, index]
- getElementsWithErrorsAndWarnings(): Array<{id: number, index: number}> {
+ getElementsWithErrorsAndWarnings(): ErrorAndWarningTuples {
+ if (!this._shouldShowWarningsAndErrors) {
+ return [];
+ }
+
if (this._cachedErrorAndWarningTuples !== null) {
return this._cachedErrorAndWarningTuples;
}
@@ -615,6 +632,10 @@ export default class Store extends EventEmitter<{
errorCount: number,
warningCount: number,
} {
+ if (!this._shouldShowWarningsAndErrors) {
+ return {errorCount: 0, warningCount: 0};
+ }
+
return this._errorsAndWarnings.get(id) || {errorCount: 0, warningCount: 0};
}
@@ -1325,16 +1346,21 @@ export default class Store extends EventEmitter<{
this._cachedErrorAndWarningTuples = null;
if (haveErrorsOrWarningsChanged) {
- let errorCount = 0;
- let warningCount = 0;
+ let componentWithErrorCount = 0;
+ let componentWithWarningCount = 0;
this._errorsAndWarnings.forEach(entry => {
- errorCount += entry.errorCount;
- warningCount += entry.warningCount;
+ if (entry.errorCount > 0) {
+ componentWithErrorCount++;
+ }
+
+ if (entry.warningCount > 0) {
+ componentWithWarningCount++;
+ }
});
- this._cachedErrorCount = errorCount;
- this._cachedWarningCount = warningCount;
+ this._cachedComponentWithErrorCount = componentWithErrorCount;
+ this._cachedComponentWithWarningCount = componentWithWarningCount;
}
if (haveRootsChanged) {
@@ -1528,9 +1554,21 @@ export default class Store extends EventEmitter<{
onHookSettings: (settings: $ReadOnly) => void =
settings => {
this._hookSettings = settings;
+
+ this.setShouldShowWarningsAndErrors(settings.showInlineWarningsAndErrors);
this.emit('hookSettings', settings);
};
+ setShouldShowWarningsAndErrors(status: boolean): void {
+ const previousStatus = this._shouldShowWarningsAndErrors;
+ this._shouldShowWarningsAndErrors = status;
+
+ if (previousStatus !== status) {
+ // Propagate to subscribers, although tree state has not changed
+ this.emit('mutated', [[], new Map()]);
+ }
+ }
+
// The Store should never throw an Error without also emitting an event.
// Otherwise Store errors will be invisible to users,
// but the downstream errors they cause will be reported as bugs.
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Element.js b/packages/react-devtools-shared/src/devtools/views/Components/Element.js
index f5c30d2d2b0d6..48bfbe90906ff 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/Element.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/Element.js
@@ -12,7 +12,6 @@ import {Fragment, useContext, useMemo, useState} from 'react';
import Store from 'react-devtools-shared/src/devtools/store';
import ButtonIcon from '../ButtonIcon';
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
-import {SettingsContext} from '../Settings/SettingsContext';
import {StoreContext} from '../context';
import {useSubscription} from '../hooks';
import {logEvent} from 'react-devtools-shared/src/Logger';
@@ -37,7 +36,6 @@ export default function Element({data, index, style}: Props): React.Node {
const {ownerFlatTree, ownerID, selectedElementID} =
useContext(TreeStateContext);
const dispatch = useContext(TreeDispatcherContext);
- const {showInlineWarningsAndErrors} = React.useContext(SettingsContext);
const element =
ownerFlatTree !== null
@@ -181,7 +179,7 @@ export default function Element({data, index, style}: Props): React.Node {
className={styles.BadgesBlock}
/>
- {showInlineWarningsAndErrors && errorCount > 0 && (
+ {errorCount > 0 && (
)}
- {showInlineWarningsAndErrors && warningCount > 0 && (
+ {warningCount > 0 && (
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js
index 136baa1205de2..db1bf9a98c135 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js
@@ -73,7 +73,7 @@ export default function Tree(props: Props): React.Node {
const [treeFocused, setTreeFocused] = useState(false);
- const {lineHeight, showInlineWarningsAndErrors} = useContext(SettingsContext);
+ const {lineHeight} = useContext(SettingsContext);
// Make sure a newly selected element is visible in the list.
// This is helpful for things like the owners list and search.
@@ -325,8 +325,8 @@ export default function Tree(props: Props): React.Node {
const errorsOrWarningsSubscription = useMemo(
() => ({
getCurrentValue: () => ({
- errors: store.errorCount,
- warnings: store.warningCount,
+ errors: store.componentWithErrorCount,
+ warnings: store.componentWithWarningCount,
}),
subscribe: (callback: Function) => {
store.addListener('mutated', callback);
@@ -370,40 +370,38 @@ export default function Tree(props: Props): React.Node {
}>
{ownerID !== null ? : }
- {showInlineWarningsAndErrors &&
- ownerID === null &&
- (errors > 0 || warnings > 0) && (
-
-
- {errors > 0 && (
-
-
- {errors}
-
- )}
- {warnings > 0 && (
-
-
- {warnings}
-
- )}
-
-
-
-
- )}
+ {ownerID === null && (errors > 0 || warnings > 0) && (
+
+
+ {errors > 0 && (
+
+
+ {errors}
+
+ )}
+ {warnings > 0 && (
+
+
+ {warnings}
+
+ )}
+
+
+
+
+ )}
{!hideSettings && (
diff --git a/packages/react-devtools-shared/src/devtools/views/Settings/DebuggingSettings.js b/packages/react-devtools-shared/src/devtools/views/Settings/DebuggingSettings.js
index 8bde14e62e606..c48cdb58e3e4c 100644
--- a/packages/react-devtools-shared/src/devtools/views/Settings/DebuggingSettings.js
+++ b/packages/react-devtools-shared/src/devtools/views/Settings/DebuggingSettings.js
@@ -37,6 +37,10 @@ export default function DebuggingSettings({
const [showInlineWarningsAndErrors, setShowInlineWarningsAndErrors] =
useState(usedHookSettings.showInlineWarningsAndErrors);
+ useEffect(() => {
+ store.setShouldShowWarningsAndErrors(showInlineWarningsAndErrors);
+ }, [showInlineWarningsAndErrors]);
+
useEffect(() => {
store.updateHookSettings({
appendComponentStack,
diff --git a/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js b/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
index 17514d94648ac..196ea806f6aac 100644
--- a/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
+++ b/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
@@ -43,21 +43,9 @@ type Context = {
// Specified as a separate prop so it can trigger a re-render of FixedSizeList.
lineHeight: number,
- appendComponentStack: boolean,
- setAppendComponentStack: (value: boolean) => void,
-
- breakOnConsoleErrors: boolean,
- setBreakOnConsoleErrors: (value: boolean) => void,
-
parseHookNames: boolean,
setParseHookNames: (value: boolean) => void,
- hideConsoleLogsInStrictMode: boolean,
- setHideConsoleLogsInStrictMode: (value: boolean) => void,
-
- showInlineWarningsAndErrors: boolean,
- setShowInlineWarningsAndErrors: (value: boolean) => void,
-
theme: Theme,
setTheme(value: Theme): void,
@@ -176,7 +164,7 @@ function SettingsContextController({
bridge.send('setTraceUpdatesEnabled', traceUpdatesEnabled);
}, [bridge, traceUpdatesEnabled]);
- const value = useMemo(
+ const value: Context = useMemo(
() => ({
displayDensity,
lineHeight: