@samitouri / QOS-React / commits / bcf97c7564

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 <28902667+hoxyq@users.noreply.github.com>

emily8rown committed Dec 15, 2025 at 13:41 UTC bcf97c7564cbe0c903a16a8d6ff52f124f2f06ff
9 files changed +165 -15
packages/react-devtools-core/README.md
+2 -2
@@ -32,7 +32,7 @@ if (process.env.NODE_ENV !== 'production') {
32 #### `Settings`
33 | Spec | Default value |
34 |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
35 -| <pre>{<br> appendComponentStack: boolean,<br> breakOnConsoleErrors: boolean,<br> showInlineWarningsAndErrors: boolean,<br> hideConsoleLogsInStrictMode: boolean<br>}</pre> | <pre>{<br> appendComponentStack: true,<br> breakOnConsoleErrors: false,<br> showInlineWarningsAndErrors: true,<br> hideConsoleLogsInStrictMode: false<br>}</pre> |
35 +| <pre>{<br> appendComponentStack: boolean,<br> breakOnConsoleErrors: boolean,<br> showInlineWarningsAndErrors: boolean,<br> hideConsoleLogsInStrictMode: boolean,<br> disableSecondConsoleLogDimmingInStrictMode: boolean<br>}</pre> | <pre>{<br> appendComponentStack: true,<br> breakOnConsoleErrors: false,<br> showInlineWarningsAndErrors: true,<br> hideConsoleLogsInStrictMode: false,<br> disableSecondConsoleLogDimmingInStrictMode: false<br>}</pre> |
36
37 ### `connectToDevTools` options
38 | Prop | Default | Description |
@@ -53,7 +53,7 @@ if (process.env.NODE_ENV !== 'production') {
53 | `onSubscribe` | Function, which receives listener (function, with a single argument) as an argument. Called when backend subscribes to messages from the other end (frontend). |
54 | `onUnsubscribe` | Function, which receives listener (function) as an argument. Called when backend unsubscribes to messages from the other end (frontend). |
55 | `onMessage` | Function, which receives 2 arguments: event (string) and payload (any). Called when backend emits a message, which should be sent to the frontend. |
56 -| `onSettingsUpdated` | A callback that will be called when the user updates the settings in the UI. You can use it for persisting user settings. |
56 +| `onSettingsUpdated` | A callback that will be called when the user updates the settings in the UI. You can use it for persisting user settings. |
57
58 Unlike `connectToDevTools`, `connectWithCustomMessagingProtocol` returns a callback, which can be used for unsubscribing the backend from the global DevTools hook.
59
packages/react-devtools-extensions/src/contentScripts/hookSettingsInjector.js
+5
@@ -24,6 +24,11 @@ async function messageListener(event: MessageEvent) {
24 if (typeof settings.hideConsoleLogsInStrictMode !== 'boolean') {
25 settings.hideConsoleLogsInStrictMode = false;
26 }
27 + if (
28 + typeof settings.disableSecondConsoleLogDimmingInStrictMode !== 'boolean'
29 + ) {
30 + settings.disableSecondConsoleLogDimmingInStrictMode = false;
31 + }
32
33 window.postMessage({
34 source: 'react-devtools-hook-settings-injector',
packages/react-devtools-inline/src/backend.js
+5
@@ -27,6 +27,7 @@ function startActivation(contentWindow: any, bridge: BackendBridge) {
27 componentFilters,
28 showInlineWarningsAndErrors,
29 hideConsoleLogsInStrictMode,
30 + disableSecondConsoleLogDimmingInStrictMode,
31 } = data;
32
33 contentWindow.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ =
@@ -38,6 +39,8 @@ function startActivation(contentWindow: any, bridge: BackendBridge) {
39 showInlineWarningsAndErrors;
40 contentWindow.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ =
41 hideConsoleLogsInStrictMode;
42 + contentWindow.__REACT_DEVTOOLS_DISABLE_SECOND_CONSOLE_LOG_DIMMING_IN_STRICT_MODE__ =
43 + disableSecondConsoleLogDimmingInStrictMode;
44
45 // TRICKY
46 // The backend entry point may be required in the context of an iframe or the parent window.
@@ -53,6 +56,8 @@ function startActivation(contentWindow: any, bridge: BackendBridge) {
56 showInlineWarningsAndErrors;
57 window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ =
58 hideConsoleLogsInStrictMode;
59 + window.__REACT_DEVTOOLS_DISABLE_SECOND_CONSOLE_LOG_DIMMING_IN_STRICT_MODE__ =
60 + disableSecondConsoleLogDimmingInStrictMode;
61 }
62
63 finishActivation(contentWindow, bridge);
packages/react-devtools-shared/src/__tests__/console-test.js
+81
@@ -733,4 +733,85 @@ describe('console', () => {
733 : 'in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
734 ]);
735 });
736 +
737 + it('should not dim console logs if disableSecondConsoleLogDimmingInStrictMode is enabled', () => {
738 + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false;
739 + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode =
740 + false;
741 + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.disableSecondConsoleLogDimmingInStrictMode =
742 + true;
743 +
744 + const container = document.createElement('div');
745 + const root = ReactDOMClient.createRoot(container);
746 +
747 + function App() {
748 + console.log('log');
749 + console.warn('warn');
750 + console.error('error');
751 + return <div />;
752 + }
753 +
754 + act(() =>
755 + root.render(
756 + <React.StrictMode>
757 + <App />
758 + </React.StrictMode>,
759 + ),
760 + );
761 +
762 + // Both logs should be called (double logging)
763 + expect(global.consoleLogMock).toHaveBeenCalledTimes(2);
764 + expect(global.consoleWarnMock).toHaveBeenCalledTimes(2);
765 + expect(global.consoleErrorMock).toHaveBeenCalledTimes(2);
766 +
767 + // The second log should NOT have dimming (no ANSI codes)
768 + expect(global.consoleLogMock.mock.calls[1]).toEqual(['log']);
769 + expect(global.consoleWarnMock.mock.calls[1]).toEqual(['warn']);
770 + expect(global.consoleErrorMock.mock.calls[1]).toEqual(['error']);
771 + });
772 +
773 + it('should dim console logs if disableSecondConsoleLogDimmingInStrictMode is disabled', () => {
774 + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.appendComponentStack = false;
775 + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.hideConsoleLogsInStrictMode =
776 + false;
777 + global.__REACT_DEVTOOLS_GLOBAL_HOOK__.settings.disableSecondConsoleLogDimmingInStrictMode =
778 + false;
779 +
780 + const container = document.createElement('div');
781 + const root = ReactDOMClient.createRoot(container);
782 +
783 + function App() {
784 + console.log('log');
785 + console.warn('warn');
786 + console.error('error');
787 + return <div />;
788 + }
789 +
790 + act(() =>
791 + root.render(
792 + <React.StrictMode>
793 + <App />
794 + </React.StrictMode>,
795 + ),
796 + );
797 +
798 + // Both logs should be called (double logging)
799 + expect(global.consoleLogMock).toHaveBeenCalledTimes(2);
800 + expect(global.consoleWarnMock).toHaveBeenCalledTimes(2);
801 + expect(global.consoleErrorMock).toHaveBeenCalledTimes(2);
802 +
803 + // The second log should have dimming (ANSI codes present)
804 + expect(global.consoleLogMock.mock.calls[1]).toEqual([
805 + '\x1b[2;38;2;124;124;124m%s\x1b[0m',
806 + 'log',
807 + ]);
808 + expect(global.consoleWarnMock.mock.calls[1]).toEqual([
809 + '\x1b[2;38;2;124;124;124m%s\x1b[0m',
810 + 'warn',
811 + ]);
812 + expect(global.consoleErrorMock.mock.calls[1]).toEqual([
813 + '\x1b[2;38;2;124;124;124m%s\x1b[0m',
814 + 'error',
815 + ]);
816 + });
817 });
packages/react-devtools-shared/src/__tests__/setupTests.js
+1
@@ -248,6 +248,7 @@ beforeEach(() => {
248 breakOnConsoleErrors: false,
249 showInlineWarningsAndErrors: true,
250 hideConsoleLogsInStrictMode: false,
251 + disableSecondConsoleLogDimmingInStrictMode: false,
252 });
253
254 const bridgeListeners = [];
packages/react-devtools-shared/src/backend/types.js
+1
@@ -597,4 +597,5 @@ export type DevToolsHookSettings = {
597 breakOnConsoleErrors: boolean,
598 showInlineWarningsAndErrors: boolean,
599 hideConsoleLogsInStrictMode: boolean,
600 + disableSecondConsoleLogDimmingInStrictMode: boolean,
601 };
packages/react-devtools-shared/src/devtools/views/Settings/DebuggingSettings.js
+45 -2
@@ -36,6 +36,10 @@ export default function DebuggingSettings({
36 useState(usedHookSettings.hideConsoleLogsInStrictMode);
37 const [showInlineWarningsAndErrors, setShowInlineWarningsAndErrors] =
38 useState(usedHookSettings.showInlineWarningsAndErrors);
39 + const [
40 + disableSecondConsoleLogDimmingInStrictMode,
41 + setDisableSecondConsoleLogDimmingInStrictMode,
42 + ] = useState(usedHookSettings.disableSecondConsoleLogDimmingInStrictMode);
43
44 useEffect(() => {
45 store.setShouldShowWarningsAndErrors(showInlineWarningsAndErrors);
@@ -47,6 +51,7 @@ export default function DebuggingSettings({
51 breakOnConsoleErrors,
52 showInlineWarningsAndErrors,
53 hideConsoleLogsInStrictMode,
54 + disableSecondConsoleLogDimmingInStrictMode,
55 });
56 }, [
57 store,
@@ -54,6 +59,7 @@ export default function DebuggingSettings({
59 breakOnConsoleErrors,
60 showInlineWarningsAndErrors,
61 hideConsoleLogsInStrictMode,
62 + disableSecondConsoleLogDimmingInStrictMode,
63 ]);
64
65 return (
@@ -105,12 +111,49 @@ export default function DebuggingSettings({
111 <input
112 type="checkbox"
113 checked={hideConsoleLogsInStrictMode}
114 + onChange={({currentTarget}) => {
115 + setHideConsoleLogsInStrictMode(currentTarget.checked);
116 + if (currentTarget.checked) {
117 + setDisableSecondConsoleLogDimmingInStrictMode(false);
118 + }
119 + }}
120 + className={styles.SettingRowCheckbox}
121 + />
122 + Hide logs during additional invocations in&nbsp;
123 + <a
124 + className={styles.StrictModeLink}
125 + target="_blank"
126 + rel="noopener noreferrer"
127 + href="https://react.dev/reference/react/StrictMode">
128 + Strict Mode
129 + </a>
130 + </label>
131 + </div>
132 +
133 + <div
134 + className={
135 + hideConsoleLogsInStrictMode
136 + ? `${styles.SettingDisabled} ${styles.SettingWrapper}`
137 + : styles.SettingWrapper
138 + }>
139 + <label
140 + className={
141 + hideConsoleLogsInStrictMode
142 + ? `${styles.SettingDisabled} ${styles.SettingRow}`
143 + : styles.SettingRow
144 + }>
145 + <input
146 + type="checkbox"
147 + checked={disableSecondConsoleLogDimmingInStrictMode}
148 + disabled={hideConsoleLogsInStrictMode}
149 onChange={({currentTarget}) =>
109 - setHideConsoleLogsInStrictMode(currentTarget.checked)
150 + setDisableSecondConsoleLogDimmingInStrictMode(
151 + currentTarget.checked,
152 + )
153 }
154 className={styles.SettingRowCheckbox}
155 />
113 - Hide logs during additional invocations in&nbsp;
156 + Disable log dimming during additional invocations in&nbsp;
157 <a
158 className={styles.StrictModeLink}
159 target="_blank"
packages/react-devtools-shared/src/devtools/views/Settings/SettingsShared.css
+5
@@ -26,6 +26,11 @@
26 margin: 0.125rem 0.25rem 0.125rem 0;
27 }
28
29 +.SettingDisabled {
30 + opacity: 0.5;
31 + cursor: not-allowed;
32 +}
33 +
34 .OptionGroup {
35 display: inline-flex;
36 flex-direction: row;
packages/react-devtools-shared/src/hook.js
+20 -11
@@ -367,17 +367,22 @@ export function installHook(
367 return;
368 }
369
370 - // Dim the text color of the double logs if we're not hiding them.
371 - // Firefox doesn't support ANSI escape sequences
372 - if (__IS_FIREFOX__) {
373 - originalMethod(
374 - ...formatWithStyles(args, FIREFOX_CONSOLE_DIMMING_COLOR),
375 - );
370 + if (settings.disableSecondConsoleLogDimmingInStrictMode) {
371 + // Don't dim the console logs
372 + originalMethod(...args);
373 } else {
377 - originalMethod(
378 - ANSI_STYLE_DIMMING_TEMPLATE,
379 - ...formatConsoleArguments(...args),
380 - );
374 + // Dim the text color of the double logs if we're not hiding them.
375 + // Firefox doesn't support ANSI escape sequences
376 + if (__IS_FIREFOX__) {
377 + originalMethod(
378 + ...formatWithStyles(args, FIREFOX_CONSOLE_DIMMING_COLOR),
379 + );
380 + } else {
381 + originalMethod(
382 + ANSI_STYLE_DIMMING_TEMPLATE,
383 + ...formatConsoleArguments(...args),
384 + );
385 + }
386 }
387 };
388
@@ -579,7 +584,10 @@ export function installHook(
584 debugger;
585 }
586
582 - if (isRunningDuringStrictModeInvocation) {
587 + if (
588 + isRunningDuringStrictModeInvocation &&
589 + !settings.disableSecondConsoleLogDimmingInStrictMode
590 + ) {
591 // Dim the text color of the double logs if we're not hiding them.
592 // Firefox doesn't support ANSI escape sequences
593 if (__IS_FIREFOX__) {
@@ -667,6 +675,7 @@ export function installHook(
675 breakOnConsoleErrors: false,
676 showInlineWarningsAndErrors: true,
677 hideConsoleLogsInStrictMode: false,
678 + disableSecondConsoleLogDimmingInStrictMode: false,
679 };
680 patchConsoleForErrorsAndWarnings();
681 } else {