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: