mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Dim console calls on additional Effect invocations due to StrictMode (#29007)
This commit is contained in:
committed by
GitHub
parent
81c5ff2e04
commit
3ac551e855
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = [
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.ReleaseNotesLink {
|
||||
.ReleaseNotesLink, .StrictModeLink {
|
||||
color: var(--color-button-active);
|
||||
}
|
||||
|
||||
@@ -153,4 +153,4 @@
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
10
packages/react-devtools-shared/src/hook.js
vendored
10
packages/react-devtools-shared/src/hook.js
vendored
@@ -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,
|
||||
}: {
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
@@ -3675,6 +3676,7 @@ function doubleInvokeEffectsOnFiber(
|
||||
fiber: Fiber,
|
||||
shouldDoubleInvokePassiveEffects: boolean = true,
|
||||
) {
|
||||
setIsStrictModeForDevtools(true);
|
||||
disappearLayoutEffects(fiber);
|
||||
if (shouldDoubleInvokePassiveEffects) {
|
||||
disconnectPassiveEffect(fiber);
|
||||
@@ -3683,6 +3685,7 @@ function doubleInvokeEffectsOnFiber(
|
||||
if (shouldDoubleInvokePassiveEffects) {
|
||||
reconnectPassiveEffects(root, fiber, NoLanes, null, false);
|
||||
}
|
||||
setIsStrictModeForDevtools(false);
|
||||
}
|
||||
|
||||
function doubleInvokeEffectsInDEVIfNecessary(
|
||||
|
||||
@@ -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"',
|
||||
@@ -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.
|
||||
@@ -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"',
|
||||
@@ -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"',
|
||||
|
||||
@@ -1343,6 +1343,42 @@ describe('context legacy', () => {
|
||||
// and on the next render they'd get deduplicated and ignored.
|
||||
expect(console.log).toBeCalledWith('foo 1');
|
||||
});
|
||||
|
||||
it('does not disable logs for effect double invoke', async () => {
|
||||
let create = 0;
|
||||
let cleanup = 0;
|
||||
function Foo() {
|
||||
React.useEffect(() => {
|
||||
create++;
|
||||
console.log('foo create ' + create);
|
||||
return () => {
|
||||
cleanup++;
|
||||
console.log('foo cleanup ' + cleanup);
|
||||
};
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Foo />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
});
|
||||
expect(create).toBe(__DEV__ ? 2 : 1);
|
||||
expect(cleanup).toBe(__DEV__ ? 1 : 0);
|
||||
expect(console.log).toBeCalledTimes(__DEV__ ? 3 : 1);
|
||||
// Note: we should display the first log because otherwise
|
||||
// there is a risk of suppressing warnings when they happen,
|
||||
// and on the next render they'd get deduplicated and ignored.
|
||||
expect(console.log).toBeCalledWith('foo create 1');
|
||||
if (__DEV__) {
|
||||
expect(console.log).toBeCalledWith('foo cleanup 1');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
it('disable logs for class double render', async () => {
|
||||
let count = 0;
|
||||
@@ -1530,6 +1566,39 @@ describe('context legacy', () => {
|
||||
// and on the next render they'd get deduplicated and ignored.
|
||||
expect(console.log).toBeCalledWith('foo 1');
|
||||
});
|
||||
|
||||
it('disable logs for effect double invoke', async () => {
|
||||
let create = 0;
|
||||
let cleanup = 0;
|
||||
function Foo() {
|
||||
React.useEffect(() => {
|
||||
create++;
|
||||
console.log('foo create ' + create);
|
||||
return () => {
|
||||
cleanup++;
|
||||
console.log('foo cleanup ' + cleanup);
|
||||
};
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Foo />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
});
|
||||
expect(create).toBe(__DEV__ ? 2 : 1);
|
||||
expect(cleanup).toBe(__DEV__ ? 1 : 0);
|
||||
expect(console.log).toBeCalledTimes(1);
|
||||
// Note: we should display the first log because otherwise
|
||||
// there is a risk of suppressing warnings when they happen,
|
||||
// and on the next render they'd get deduplicated and ignored.
|
||||
expect(console.log).toBeCalledWith('foo create 1');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user