Skip to content

Commit

Permalink
Dim console calls on additional Effect invocations due to `StrictMo…
Browse files Browse the repository at this point in the history
…de` (#29007)
  • Loading branch information
eps1lon committed May 22, 2024
1 parent 81c5ff2 commit 3ac551e
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ describe('ReactInternalTestUtils', () => {
test('assertLog', async () => {
const Yield = ({id}) => {
Scheduler.log(id);
React.useEffect(() => {
Scheduler.log(`create effect ${id}`);
return () => {
Scheduler.log(`cleanup effect ${id}`);
};
});
return id;
};

Expand All @@ -167,7 +173,20 @@ describe('ReactInternalTestUtils', () => {
</React.StrictMode>
);
});
assertLog(['A', 'B', 'C']);
assertLog([
'A',
'B',
'C',
'create effect A',
'create effect B',
'create effect C',
]);

await act(() => {
root.render(null);
});

assertLog(['cleanup effect A', 'cleanup effect B', 'cleanup effect C']);
});
});

Expand Down
162 changes: 162 additions & 0 deletions packages/react-devtools-shared/src/__tests__/console-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,168 @@ describe('console', () => {
expect(mockGroupCollapsed.mock.calls[0][0]).toBe('groupCollapsed');
});

it('should double log from Effects if hideConsoleLogsInStrictMode is disabled in Strict mode', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;

const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);

function App() {
React.useEffect(() => {
fakeConsole.log('log effect create');
fakeConsole.warn('warn effect create');
fakeConsole.error('error effect create');
fakeConsole.info('info effect create');
fakeConsole.group('group effect create');
fakeConsole.groupCollapsed('groupCollapsed effect create');

return () => {
fakeConsole.log('log effect cleanup');
fakeConsole.warn('warn effect cleanup');
fakeConsole.error('error effect cleanup');
fakeConsole.info('info effect cleanup');
fakeConsole.group('group effect cleanup');
fakeConsole.groupCollapsed('groupCollapsed effect cleanup');
};
});

return <div />;
}

act(() =>
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
),
);
expect(mockLog.mock.calls).toEqual([
['log effect create'],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'log effect cleanup',
],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'log effect create',
],
]);
expect(mockWarn.mock.calls).toEqual([
['warn effect create'],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_WARNING_COLOR}`,
'warn effect cleanup',
],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_WARNING_COLOR}`,
'warn effect create',
],
]);
expect(mockError.mock.calls).toEqual([
['error effect create'],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_ERROR_COLOR}`,
'error effect cleanup',
],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_ERROR_COLOR}`,
'error effect create',
],
]);
expect(mockInfo.mock.calls).toEqual([
['info effect create'],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'info effect cleanup',
],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'info effect create',
],
]);
expect(mockGroup.mock.calls).toEqual([
['group effect create'],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'group effect cleanup',
],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'group effect create',
],
]);
expect(mockGroupCollapsed.mock.calls).toEqual([
['groupCollapsed effect create'],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'groupCollapsed effect cleanup',
],
[
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'groupCollapsed effect create',
],
]);
});

it('should not double log from Effects if hideConsoleLogsInStrictMode is enabled in Strict mode', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = true;

const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);

function App() {
React.useEffect(() => {
fakeConsole.log('log effect create');
fakeConsole.warn('warn effect create');
fakeConsole.error('error effect create');
fakeConsole.info('info effect create');
fakeConsole.group('group effect create');
fakeConsole.groupCollapsed('groupCollapsed effect create');

return () => {
fakeConsole.log('log effect cleanup');
fakeConsole.warn('warn effect cleanup');
fakeConsole.error('error effect cleanup');
fakeConsole.info('info effect cleanup');
fakeConsole.group('group effect cleanup');
fakeConsole.groupCollapsed('groupCollapsed effect cleanup');
};
});

return <div />;
}

act(() =>
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
),
);
expect(mockLog.mock.calls).toEqual([['log effect create']]);
expect(mockWarn.mock.calls).toEqual([['warn effect create']]);
expect(mockError.mock.calls).toEqual([['error effect create']]);
expect(mockInfo.mock.calls).toEqual([['info effect create']]);
expect(mockGroup.mock.calls).toEqual([['group effect create']]);
expect(mockGroupCollapsed.mock.calls).toEqual([
['groupCollapsed effect create'],
]);
});

it('should double log from useMemo if hideConsoleLogsInStrictMode is disabled in Strict mode', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;
Expand Down
4 changes: 2 additions & 2 deletions packages/react-devtools-shared/src/backend/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export function unpatch(): void {

let unpatchForStrictModeFn: null | (() => void) = null;

// NOTE: KEEP IN SYNC with src/hook.js:patchConsoleForInitialRenderInStrictMode
// NOTE: KEEP IN SYNC with src/hook.js:patchConsoleForInitialCommitInStrictMode
export function patchForStrictMode() {
if (consoleManagedByDevToolsDuringStrictMode) {
const overrideConsoleMethods = [
Expand Down Expand Up @@ -359,7 +359,7 @@ export function patchForStrictMode() {
}
}

// NOTE: KEEP IN SYNC with src/hook.js:unpatchConsoleForInitialRenderInStrictMode
// NOTE: KEEP IN SYNC with src/hook.js:unpatchConsoleForInitialCommitInStrictMode
export function unpatchForStrictMode(): void {
if (consoleManagedByDevToolsDuringStrictMode) {
if (unpatchForStrictModeFn !== null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ export default function DebuggingSettings(_: {}): React.Node {
setHideConsoleLogsInStrictMode(currentTarget.checked)
}
/>{' '}
Hide logs during second render in Strict Mode
Hide logs during additional invocations in{' '}
<a
className={styles.StrictModeLink}
target="_blank"
rel="noopener noreferrer"
href="https://react.dev/reference/react/StrictMode">
Strict Mode
</a>
</label>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
border-radius: 0.25rem;
}

.ReleaseNotesLink {
.ReleaseNotesLink, .StrictModeLink {
color: var(--color-button-active);
}

Expand All @@ -153,4 +153,4 @@
list-style: none;
padding: 0;
margin: 0;
}
}
10 changes: 5 additions & 5 deletions packages/react-devtools-shared/src/hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export function installHook(target: any): DevToolsHook | null {
// React and DevTools are connecting and the renderer interface isn't avaiable
// and we want to be able to have consistent logging behavior for double logs
// during the initial renderer.
function patchConsoleForInitialRenderInStrictMode({
function patchConsoleForInitialCommitInStrictMode({
hideConsoleLogsInStrictMode,
browserTheme,
}: {
Expand Down Expand Up @@ -311,7 +311,7 @@ export function installHook(target: any): DevToolsHook | null {
}

// NOTE: KEEP IN SYNC with src/backend/console.js:unpatchForStrictMode
function unpatchConsoleForInitialRenderInStrictMode() {
function unpatchConsoleForInitialCommitInStrictMode() {
if (unpatchFn !== null) {
unpatchFn();
unpatchFn = null;
Expand Down Expand Up @@ -451,19 +451,19 @@ export function installHook(target: any): DevToolsHook | null {
rendererInterface.unpatchConsoleForStrictMode();
}
} else {
// This should only happen during initial render in the extension before DevTools
// This should only happen during initial commit in the extension before DevTools
// finishes its handshake with the injected renderer
if (isStrictMode) {
const hideConsoleLogsInStrictMode =
window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ === true;
const browserTheme = window.__REACT_DEVTOOLS_BROWSER_THEME__;

patchConsoleForInitialRenderInStrictMode({
patchConsoleForInitialCommitInStrictMode({
hideConsoleLogsInStrictMode,
browserTheme,
});
} else {
unpatchConsoleForInitialRenderInStrictMode();
unpatchConsoleForInitialCommitInStrictMode();
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/react-reconciler/src/ReactFiberWorkLoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ import {
markRenderStopped,
onCommitRoot as onCommitRootDevTools,
onPostCommitRoot as onPostCommitRootDevTools,
setIsStrictModeForDevtools,
} from './ReactFiberDevToolsHook';
import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
import {releaseCache} from './ReactFiberCacheComponent';
Expand Down Expand Up @@ -3675,6 +3676,7 @@ function doubleInvokeEffectsOnFiber(
fiber: Fiber,
shouldDoubleInvokePassiveEffects: boolean = true,
) {
setIsStrictModeForDevtools(true);
disappearLayoutEffects(fiber);
if (shouldDoubleInvokePassiveEffects) {
disconnectPassiveEffect(fiber);
Expand All @@ -3683,6 +3685,7 @@ function doubleInvokeEffectsOnFiber(
if (shouldDoubleInvokePassiveEffects) {
reconnectPassiveEffects(root, fiber, NoLanes, null, false);
}
setIsStrictModeForDevtools(false);
}

function doubleInvokeEffectsInDEVIfNecessary(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,7 @@ describe('StrictEffectsMode defaults', () => {
</React.StrictMode>,
);

await waitForPaint([
'useLayoutEffect mount "one"',
'useLayoutEffect unmount "one"',
'useLayoutEffect mount "one"',
]);
await waitForPaint(['useLayoutEffect mount "one"']);
expect(log).toEqual([
'useLayoutEffect mount "one"',
'useLayoutEffect unmount "one"',
Expand All @@ -142,10 +138,6 @@ describe('StrictEffectsMode defaults', () => {
'useLayoutEffect unmount "one"',
'useLayoutEffect mount "one"',
'useLayoutEffect mount "two"',

// Since "two" is new, it should be double-invoked.
'useLayoutEffect unmount "two"',
'useLayoutEffect mount "two"',
]);
expect(log).toEqual([
// Cleanup and re-run "one" (and "two") since there is no dependencies array.
Expand Down Expand Up @@ -196,10 +188,6 @@ describe('StrictEffectsMode defaults', () => {
await waitForAll([
'useLayoutEffect mount "one"',
'useEffect mount "one"',
'useLayoutEffect unmount "one"',
'useEffect unmount "one"',
'useLayoutEffect mount "one"',
'useEffect mount "one"',
]);
expect(log).toEqual([
'useLayoutEffect mount "one"',
Expand Down Expand Up @@ -237,12 +225,6 @@ describe('StrictEffectsMode defaults', () => {
'useEffect unmount "one"',
'useEffect mount "one"',
'useEffect mount "two"',

// Since "two" is new, it should be double-invoked.
'useLayoutEffect unmount "two"',
'useEffect unmount "two"',
'useLayoutEffect mount "two"',
'useEffect mount "two"',
]);
expect(log).toEqual([
'useEffect unmount "one"',
Expand Down

0 comments on commit 3ac551e

Please sign in to comment.