Avoid passing custom stacks to console.error (#18685)

* Detect double stacks in the new format in tests

* Remove unnecessary uses of getStackByFiberInDevAndProd

These all execute in the right execution context already.

* Set the debug fiber around the cases that don't have an execution context

* Remove stack detection in our console log overrides

We never pass custom stacks as part of the args anymore.

* Bonus: Don't append getStackAddendum to invariants

We print component stacks for every error anyway so this is just duplicate
information.
This commit is contained in:
Sebastian Markbåge 2020-04-21 09:22:46 -07:00 committed by GitHub
parent ff431b7fc4
commit 940f48b999
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 323 additions and 402 deletions

View File

@ -15,15 +15,6 @@ let ReactDOMServer;
let ReactTestUtils;
describe('ReactComponent', () => {
function normalizeCodeLocInfo(str) {
return (
str &&
str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) {
return '\n in ' + name + ' (at **)';
})
);
}
beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');
@ -470,20 +461,11 @@ describe('ReactComponent', () => {
};
const element = <div>{[children]}</div>;
const container = document.createElement('div');
let ex;
try {
expect(() => {
ReactDOM.render(element, container);
} catch (e) {
ex = e;
}
expect(ex).toBeDefined();
expect(normalizeCodeLocInfo(ex.message)).toBe(
'Objects are not valid as a React child (found: object with keys {x, y, z}).' +
(__DEV__
? ' If you meant to render a collection of children, use ' +
'an array instead.' +
'\n in div (at **)'
: ''),
}).toThrowError(
'Objects are not valid as a React child (found: object with keys {x, y, z}). ' +
'If you meant to render a collection of children, use an array instead.',
);
});
@ -499,21 +481,12 @@ describe('ReactComponent', () => {
}
}
const container = document.createElement('div');
let ex;
try {
expect(() => {
ReactDOM.render(<Foo />, container);
} catch (e) {
ex = e;
}
expect(ex).toBeDefined();
expect(normalizeCodeLocInfo(ex.message)).toBe(
}).toThrowError(
'Objects are not valid as a React child (found: object with keys {a, b, c}).' +
(__DEV__
? ' If you meant to render a collection of children, use ' +
'an array instead.\n' +
' in div (at **)\n' +
' in Foo (at **)'
: ''),
' If you meant to render a collection of children, use an array ' +
'instead.',
);
});
@ -524,20 +497,12 @@ describe('ReactComponent', () => {
z: <span />,
};
const element = <div>{[children]}</div>;
let ex;
try {
expect(() => {
ReactDOMServer.renderToString(element);
} catch (e) {
ex = e;
}
expect(ex).toBeDefined();
expect(normalizeCodeLocInfo(ex.message)).toBe(
'Objects are not valid as a React child (found: object with keys {x, y, z}).' +
(__DEV__
? ' If you meant to render a collection of children, use ' +
'an array instead.' +
'\n in div (at **)'
: ''),
}).toThrowError(
'Objects are not valid as a React child (found: object with keys {x, y, z}). ' +
'If you meant to render a collection of children, use ' +
'an array instead.',
);
});
@ -553,21 +518,12 @@ describe('ReactComponent', () => {
}
}
const container = document.createElement('div');
let ex;
try {
expect(() => {
ReactDOMServer.renderToString(<Foo />, container);
} catch (e) {
ex = e;
}
expect(ex).toBeDefined();
expect(normalizeCodeLocInfo(ex.message)).toBe(
'Objects are not valid as a React child (found: object with keys {a, b, c}).' +
(__DEV__
? ' If you meant to render a collection of children, use ' +
'an array instead.\n' +
' in div (at **)\n' +
' in Foo (at **)'
: ''),
}).toThrowError(
'Objects are not valid as a React child (found: object with keys {a, b, c}). ' +
'If you meant to render a collection of children, use ' +
'an array instead.',
);
});

View File

@ -16,15 +16,6 @@ describe('ReactDOMComponent', () => {
let ReactDOMServer;
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
function normalizeCodeLocInfo(str) {
return (
str &&
str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) {
return '\n in ' + name + ' (at **)';
})
);
}
beforeEach(() => {
jest.resetModules();
React = require('react');
@ -1320,36 +1311,24 @@ describe('ReactDOMComponent', () => {
it('should throw on children for void elements', () => {
const container = document.createElement('div');
let caughtErr;
try {
expect(() => {
ReactDOM.render(<input>children</input>, container);
} catch (err) {
caughtErr = err;
}
expect(caughtErr).not.toBe(undefined);
expect(normalizeCodeLocInfo(caughtErr.message)).toContain(
}).toThrowError(
'input is a void element tag and must neither have `children` nor ' +
'use `dangerouslySetInnerHTML`.' +
(__DEV__ ? '\n in input (at **)' : ''),
'use `dangerouslySetInnerHTML`.',
);
});
it('should throw on dangerouslySetInnerHTML for void elements', () => {
const container = document.createElement('div');
let caughtErr;
try {
expect(() => {
ReactDOM.render(
<input dangerouslySetInnerHTML={{__html: 'content'}} />,
container,
);
} catch (err) {
caughtErr = err;
}
expect(caughtErr).not.toBe(undefined);
expect(normalizeCodeLocInfo(caughtErr.message)).toContain(
}).toThrowError(
'input is a void element tag and must neither have `children` nor ' +
'use `dangerouslySetInnerHTML`.' +
(__DEV__ ? '\n in input (at **)' : ''),
'use `dangerouslySetInnerHTML`.',
);
});
@ -1461,18 +1440,11 @@ describe('ReactDOMComponent', () => {
}
const container = document.createElement('div');
let caughtErr;
try {
expect(() => {
ReactDOM.render(<X />, container);
} catch (err) {
caughtErr = err;
}
expect(caughtErr).not.toBe(undefined);
expect(normalizeCodeLocInfo(caughtErr.message)).toContain(
}).toThrowError(
'input is a void element tag and must neither have `children` ' +
'nor use `dangerouslySetInnerHTML`.' +
(__DEV__ ? '\n in input (at **)' + '\n in X (at **)' : ''),
'nor use `dangerouslySetInnerHTML`.',
);
});
@ -1627,19 +1599,12 @@ describe('ReactDOMComponent', () => {
}
}
let caughtErr;
try {
expect(() => {
ReactDOM.render(<Animal />, container);
} catch (err) {
caughtErr = err;
}
expect(caughtErr).not.toBe(undefined);
expect(normalizeCodeLocInfo(caughtErr.message)).toContain(
}).toThrowError(
'The `style` prop expects a mapping from style properties to values, ' +
"not a string. For example, style={{marginRight: spacing + 'em'}} " +
'when using JSX.' +
(__DEV__ ? '\n in div (at **)' + '\n in Animal (at **)' : ''),
'when using JSX.',
);
});

View File

@ -17,15 +17,6 @@ let ReactCurrentDispatcher;
const enableSuspenseServerRenderer = require('shared/ReactFeatureFlags')
.enableSuspenseServerRenderer;
function normalizeCodeLocInfo(str) {
return (
str &&
str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) {
return '\n in ' + name + ' (at **)';
})
);
}
describe('ReactDOMServer', () => {
beforeEach(() => {
jest.resetModules();
@ -172,17 +163,11 @@ describe('ReactDOMServer', () => {
});
it('should throw prop mapping error for an <iframe /> with invalid props', () => {
let caughtErr;
try {
expect(() => {
ReactDOMServer.renderToString(<iframe style="border:none;" />);
} catch (err) {
caughtErr = err;
}
expect(caughtErr).not.toBe(undefined);
expect(normalizeCodeLocInfo(caughtErr.message)).toContain(
}).toThrowError(
'The `style` prop expects a mapping from style properties to values, not ' +
"a string. For example, style={{marginRight: spacing + 'em'}} when using JSX." +
(__DEV__ ? '\n in iframe (at **)' : ''),
"a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.",
);
});

View File

@ -6,19 +6,11 @@
*/
import invariant from 'shared/invariant';
// TODO: We can remove this if we add invariantWithStack()
// or add stack by default to invariants where possible.
import ReactSharedInternals from 'shared/ReactSharedInternals';
import voidElementTags from './voidElementTags';
const HTML = '__html';
let ReactDebugCurrentFrame = null;
if (__DEV__) {
ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
}
function assertValidProps(tag: string, props: ?Object) {
if (!props) {
return;
@ -28,9 +20,8 @@ function assertValidProps(tag: string, props: ?Object) {
invariant(
props.children == null && props.dangerouslySetInnerHTML == null,
'%s is a void element tag and must neither have `children` nor ' +
'use `dangerouslySetInnerHTML`.%s',
'use `dangerouslySetInnerHTML`.',
tag,
__DEV__ ? ReactDebugCurrentFrame.getStackAddendum() : '',
);
}
if (props.dangerouslySetInnerHTML != null) {
@ -64,8 +55,7 @@ function assertValidProps(tag: string, props: ?Object) {
props.style == null || typeof props.style === 'object',
'The `style` prop expects a mapping from style properties to values, ' +
"not a string. For example, style={{marginRight: spacing + 'em'}} when " +
'using JSX.%s',
__DEV__ ? ReactDebugCurrentFrame.getStackAddendum() : '',
'using JSX.',
);
}

View File

@ -8,14 +8,8 @@
*/
import invariant from 'shared/invariant';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {disableJavaScriptURLs} from 'shared/ReactFeatureFlags';
let ReactDebugCurrentFrame = ((null: any): {getStackAddendum(): string, ...});
if (__DEV__) {
ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
}
// A javascript: URL can contain leading C0 control or \u0020 SPACE,
// and any newline or tab are filtered out as if they're not part of the URL.
// https://url.spec.whatwg.org/#url-parsing
@ -34,8 +28,7 @@ function sanitizeURL(url: string) {
if (disableJavaScriptURLs) {
invariant(
!isJavaScriptProtocol.test(url),
'React has blocked a javascript: URL as a security precaution.%s',
__DEV__ ? ReactDebugCurrentFrame.getStackAddendum() : '',
'React has blocked a javascript: URL as a security precaution.',
);
} else if (__DEV__) {
if (!didWarn && isJavaScriptProtocol.test(url)) {

View File

@ -44,7 +44,6 @@ import {
createFiberFromPortal,
} from './ReactFiber.new';
import {emptyRefsObject} from './ReactFiberClassComponent.new';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {getCurrentFiberStackInDev} from './ReactCurrentFiber';
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.new';
import {StrictMode} from './ReactTypeOfMode';
@ -136,10 +135,9 @@ function coerceRef(
'will be removed in a future major release. We recommend using ' +
'useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-string-ref%s',
'https://fb.me/react-strict-mode-string-ref',
componentName,
mixedRef,
getStackByFiberInDevAndProd(returnFiber),
);
} else {
console.error(
@ -147,9 +145,8 @@ function coerceRef(
'String refs are a source of potential bugs and should be avoided. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-string-ref%s',
'https://fb.me/react-strict-mode-string-ref',
mixedRef,
getStackByFiberInDevAndProd(returnFiber),
);
}
didWarnAboutStringRefs[componentName] = true;
@ -223,20 +220,14 @@ function coerceRef(
function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
if (returnFiber.type !== 'textarea') {
let addendum = '';
if (__DEV__) {
addendum =
' If you meant to render a collection of children, use an array ' +
'instead.' +
getCurrentFiberStackInDev();
}
invariant(
false,
'Objects are not valid as a React child (found: %s).%s',
'Objects are not valid as a React child (found: %s). ' +
'If you meant to render a collection of children, use an array ' +
'instead.',
Object.prototype.toString.call(newChild) === '[object Object]'
? 'object with keys {' + Object.keys(newChild).join(', ') + '}'
: newChild,
addendum,
);
}
}

View File

@ -44,7 +44,6 @@ import {
createFiberFromPortal,
} from './ReactFiber.old';
import {emptyRefsObject} from './ReactFiberClassComponent.old';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {getCurrentFiberStackInDev} from './ReactCurrentFiber';
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.old';
import {StrictMode} from './ReactTypeOfMode';
@ -136,10 +135,9 @@ function coerceRef(
'will be removed in a future major release. We recommend using ' +
'useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-string-ref%s',
'https://fb.me/react-strict-mode-string-ref',
componentName,
mixedRef,
getStackByFiberInDevAndProd(returnFiber),
);
} else {
console.error(
@ -147,9 +145,8 @@ function coerceRef(
'String refs are a source of potential bugs and should be avoided. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-string-ref%s',
'https://fb.me/react-strict-mode-string-ref',
mixedRef,
getStackByFiberInDevAndProd(returnFiber),
);
}
didWarnAboutStringRefs[componentName] = true;
@ -223,20 +220,13 @@ function coerceRef(
function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
if (returnFiber.type !== 'textarea') {
let addendum = '';
if (__DEV__) {
addendum =
' If you meant to render a collection of children, use an array ' +
'instead.' +
getCurrentFiberStackInDev();
}
invariant(
false,
'Objects are not valid as a React child (found: %s).%s',
'Objects are not valid as a React child (found: %s). ' +
'If you meant to render a collection of children, use an array instead.',
Object.prototype.toString.call(newChild) === '[object Object]'
? 'object with keys {' + Object.keys(newChild).join(', ') + '}'
: newChild,
addendum,
);
}
}

View File

@ -73,7 +73,6 @@ import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
import {onCommitUnmount} from './ReactFiberDevToolsHook.new';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
import {
getCommitTime,
@ -368,9 +367,8 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
}
console.error(
'An effect function must not return anything besides a function, ' +
'which is used for clean-up.%s%s',
'which is used for clean-up.%s',
addendum,
getStackByFiberInDevAndProd(finishedWork),
);
}
}
@ -890,9 +888,8 @@ function commitAttachRef(finishedWork: Fiber) {
if (!ref.hasOwnProperty('current')) {
console.error(
'Unexpected ref object provided for %s. ' +
'Use either a ref-setter function or React.createRef().%s',
'Use either a ref-setter function or React.createRef().',
getComponentName(finishedWork.type),
getStackByFiberInDevAndProd(finishedWork),
);
}
}

View File

@ -73,7 +73,6 @@ import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
import {onCommitUnmount} from './ReactFiberDevToolsHook.old';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
import {
getCommitTime,
@ -368,9 +367,8 @@ function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
}
console.error(
'An effect function must not return anything besides a function, ' +
'which is used for clean-up.%s%s',
'which is used for clean-up.%s',
addendum,
getStackByFiberInDevAndProd(finishedWork),
);
}
}
@ -890,9 +888,8 @@ function commitAttachRef(finishedWork: Fiber) {
if (!ref.hasOwnProperty('current')) {
console.error(
'Unexpected ref object provided for %s. ' +
'Use either a ref-setter function or React.createRef().%s',
'Use either a ref-setter function or React.createRef().',
getComponentName(finishedWork.type),
getStackByFiberInDevAndProd(finishedWork),
);
}
}

View File

@ -66,10 +66,11 @@ import {
act,
} from './ReactFiberWorkLoop.new';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue.new';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {
isRendering as ReactCurrentFiberIsRendering,
current as ReactCurrentFiberCurrent,
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import {StrictMode} from './ReactTypeOfMode';
import {
@ -176,30 +177,41 @@ function findHostInstanceWithWarning(
const componentName = getComponentName(fiber.type) || 'Component';
if (!didWarnAboutFindNodeInStrictMode[componentName]) {
didWarnAboutFindNodeInStrictMode[componentName] = true;
if (fiber.mode & StrictMode) {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which is inside StrictMode. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-find-node%s',
methodName,
methodName,
componentName,
getStackByFiberInDevAndProd(hostFiber),
);
} else {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which renders StrictMode children. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-find-node%s',
methodName,
methodName,
componentName,
getStackByFiberInDevAndProd(hostFiber),
);
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(hostFiber);
if (fiber.mode & StrictMode) {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which is inside StrictMode. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-find-node',
methodName,
methodName,
componentName,
);
} else {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which renders StrictMode children. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-find-node',
methodName,
methodName,
componentName,
);
}
} finally {
// Ideally this should reset to previous but this shouldn't be called in
// render and there's another warning for that anyway.
if (previousFiber) {
setCurrentDebugFiberInDEV(previousFiber);
} else {
resetCurrentDebugFiberInDEV();
}
}
}
}

View File

@ -66,10 +66,11 @@ import {
act,
} from './ReactFiberWorkLoop.old';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue.old';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {
isRendering as ReactCurrentFiberIsRendering,
current as ReactCurrentFiberCurrent,
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import {StrictMode} from './ReactTypeOfMode';
import {
@ -176,30 +177,41 @@ function findHostInstanceWithWarning(
const componentName = getComponentName(fiber.type) || 'Component';
if (!didWarnAboutFindNodeInStrictMode[componentName]) {
didWarnAboutFindNodeInStrictMode[componentName] = true;
if (fiber.mode & StrictMode) {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which is inside StrictMode. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-find-node%s',
methodName,
methodName,
componentName,
getStackByFiberInDevAndProd(hostFiber),
);
} else {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which renders StrictMode children. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-find-node%s',
methodName,
methodName,
componentName,
getStackByFiberInDevAndProd(hostFiber),
);
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(hostFiber);
if (fiber.mode & StrictMode) {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which is inside StrictMode. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-find-node',
methodName,
methodName,
componentName,
);
} else {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which renders StrictMode children. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-find-node',
methodName,
methodName,
componentName,
);
}
} finally {
// Ideally this should reset to previous but this shouldn't be called in
// render and there's another warning for that anyway.
if (previousFiber) {
setCurrentDebugFiberInDEV(previousFiber);
} else {
resetCurrentDebugFiberInDEV();
}
}
}
}

View File

@ -180,9 +180,9 @@ import {
// DEV stuff
import getComponentName from 'shared/getComponentName';
import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {
isRendering as ReactCurrentDebugFiberIsRenderingInDEV,
current as ReactCurrentFiberCurrent,
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
@ -2927,15 +2927,24 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
// 1. Updating an ancestor that a component had registered itself with on mount.
// 2. Resetting state when a component is hidden after going offscreen.
} else {
console.error(
"Can't perform a React state update on an unmounted component. This " +
'is a no-op, but it indicates a memory leak in your application. To ' +
'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
tag === ClassComponent
? 'the componentWillUnmount method'
: 'a useEffect cleanup function',
getStackByFiberInDevAndProd(fiber),
);
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(fiber);
console.error(
"Can't perform a React state update on an unmounted component. This " +
'is a no-op, but it indicates a memory leak in your application. To ' +
'fix, cancel all subscriptions and asynchronous tasks in %s.',
tag === ClassComponent
? 'the componentWillUnmount method'
: 'a useEffect cleanup function',
);
} finally {
if (previousFiber) {
setCurrentDebugFiberInDEV(fiber);
} else {
resetCurrentDebugFiberInDEV();
}
}
}
}
}
@ -3072,25 +3081,33 @@ export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
IsSomeRendererActing.current === true &&
IsThisRendererActing.current !== true
) {
console.error(
"It looks like you're using the wrong act() around your test interactions.\n" +
'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
'// for react-dom:\n' +
// Break up imports to avoid accidentally parsing them as dependencies.
'import {act} fr' +
"om 'react-dom/test-utils';\n" +
'// ...\n' +
'act(() => ...);\n\n' +
'// for react-test-renderer:\n' +
// Break up imports to avoid accidentally parsing them as dependencies.
'import TestRenderer fr' +
"om react-test-renderer';\n" +
'const {act} = TestRenderer;\n' +
'// ...\n' +
'act(() => ...);' +
'%s',
getStackByFiberInDevAndProd(fiber),
);
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(fiber);
console.error(
"It looks like you're using the wrong act() around your test interactions.\n" +
'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
'// for react-dom:\n' +
// Break up imports to avoid accidentally parsing them as dependencies.
'import {act} fr' +
"om 'react-dom/test-utils';\n" +
'// ...\n' +
'act(() => ...);\n\n' +
'// for react-test-renderer:\n' +
// Break up imports to avoid accidentally parsing them as dependencies.
'import TestRenderer fr' +
"om react-test-renderer';\n" +
'const {act} = TestRenderer;\n' +
'// ...\n' +
'act(() => ...);',
);
} finally {
if (previousFiber) {
setCurrentDebugFiberInDEV(fiber);
} else {
resetCurrentDebugFiberInDEV();
}
}
}
}
}
@ -3113,10 +3130,8 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
'/* assert on the output */\n\n' +
"This ensures that you're testing the behavior the user would see " +
'in the browser.' +
' Learn more at https://fb.me/react-wrap-tests-with-act' +
'%s',
' Learn more at https://fb.me/react-wrap-tests-with-act',
getComponentName(fiber.type),
getStackByFiberInDevAndProd(fiber),
);
}
}
@ -3130,21 +3145,29 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
IsSomeRendererActing.current === false &&
IsThisRendererActing.current === false
) {
console.error(
'An update to %s inside a test was not wrapped in act(...).\n\n' +
'When testing, code that causes React state updates should be ' +
'wrapped into act(...):\n\n' +
'act(() => {\n' +
' /* fire events that update state */\n' +
'});\n' +
'/* assert on the output */\n\n' +
"This ensures that you're testing the behavior the user would see " +
'in the browser.' +
' Learn more at https://fb.me/react-wrap-tests-with-act' +
'%s',
getComponentName(fiber.type),
getStackByFiberInDevAndProd(fiber),
);
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(fiber);
console.error(
'An update to %s inside a test was not wrapped in act(...).\n\n' +
'When testing, code that causes React state updates should be ' +
'wrapped into act(...):\n\n' +
'act(() => {\n' +
' /* fire events that update state */\n' +
'});\n' +
'/* assert on the output */\n\n' +
"This ensures that you're testing the behavior the user would see " +
'in the browser.' +
' Learn more at https://fb.me/react-wrap-tests-with-act',
getComponentName(fiber.type),
);
} finally {
if (previousFiber) {
setCurrentDebugFiberInDEV(fiber);
} else {
resetCurrentDebugFiberInDEV();
}
}
}
}
}

View File

@ -178,9 +178,9 @@ import {
// DEV stuff
import getComponentName from 'shared/getComponentName';
import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {
isRendering as ReactCurrentDebugFiberIsRenderingInDEV,
current as ReactCurrentFiberCurrent,
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
@ -2949,15 +2949,24 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
// 1. Updating an ancestor that a component had registered itself with on mount.
// 2. Resetting state when a component is hidden after going offscreen.
} else {
console.error(
"Can't perform a React state update on an unmounted component. This " +
'is a no-op, but it indicates a memory leak in your application. To ' +
'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
tag === ClassComponent
? 'the componentWillUnmount method'
: 'a useEffect cleanup function',
getStackByFiberInDevAndProd(fiber),
);
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(fiber);
console.error(
"Can't perform a React state update on an unmounted component. This " +
'is a no-op, but it indicates a memory leak in your application. To ' +
'fix, cancel all subscriptions and asynchronous tasks in %s.',
tag === ClassComponent
? 'the componentWillUnmount method'
: 'a useEffect cleanup function',
);
} finally {
if (previousFiber) {
setCurrentDebugFiberInDEV(fiber);
} else {
resetCurrentDebugFiberInDEV();
}
}
}
}
}
@ -3094,25 +3103,33 @@ export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
IsSomeRendererActing.current === true &&
IsThisRendererActing.current !== true
) {
console.error(
"It looks like you're using the wrong act() around your test interactions.\n" +
'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
'// for react-dom:\n' +
// Break up imports to avoid accidentally parsing them as dependencies.
'import {act} fr' +
"om 'react-dom/test-utils';\n" +
'// ...\n' +
'act(() => ...);\n\n' +
'// for react-test-renderer:\n' +
// Break up imports to avoid accidentally parsing them as dependencies.
'import TestRenderer fr' +
"om react-test-renderer';\n" +
'const {act} = TestRenderer;\n' +
'// ...\n' +
'act(() => ...);' +
'%s',
getStackByFiberInDevAndProd(fiber),
);
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(fiber);
console.error(
"It looks like you're using the wrong act() around your test interactions.\n" +
'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
'// for react-dom:\n' +
// Break up imports to avoid accidentally parsing them as dependencies.
'import {act} fr' +
"om 'react-dom/test-utils';\n" +
'// ...\n' +
'act(() => ...);\n\n' +
'// for react-test-renderer:\n' +
// Break up imports to avoid accidentally parsing them as dependencies.
'import TestRenderer fr' +
"om react-test-renderer';\n" +
'const {act} = TestRenderer;\n' +
'// ...\n' +
'act(() => ...);',
);
} finally {
if (previousFiber) {
setCurrentDebugFiberInDEV(fiber);
} else {
resetCurrentDebugFiberInDEV();
}
}
}
}
}
@ -3135,10 +3152,8 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
'/* assert on the output */\n\n' +
"This ensures that you're testing the behavior the user would see " +
'in the browser.' +
' Learn more at https://fb.me/react-wrap-tests-with-act' +
'%s',
' Learn more at https://fb.me/react-wrap-tests-with-act',
getComponentName(fiber.type),
getStackByFiberInDevAndProd(fiber),
);
}
}
@ -3152,21 +3167,29 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
IsSomeRendererActing.current === false &&
IsThisRendererActing.current === false
) {
console.error(
'An update to %s inside a test was not wrapped in act(...).\n\n' +
'When testing, code that causes React state updates should be ' +
'wrapped into act(...):\n\n' +
'act(() => {\n' +
' /* fire events that update state */\n' +
'});\n' +
'/* assert on the output */\n\n' +
"This ensures that you're testing the behavior the user would see " +
'in the browser.' +
' Learn more at https://fb.me/react-wrap-tests-with-act' +
'%s',
getComponentName(fiber.type),
getStackByFiberInDevAndProd(fiber),
);
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(fiber);
console.error(
'An update to %s inside a test was not wrapped in act(...).\n\n' +
'When testing, code that causes React state updates should be ' +
'wrapped into act(...):\n\n' +
'act(() => {\n' +
' /* fire events that update state */\n' +
'});\n' +
'/* assert on the output */\n\n' +
"This ensures that you're testing the behavior the user would see " +
'in the browser.' +
' Learn more at https://fb.me/react-wrap-tests-with-act',
getComponentName(fiber.type),
);
} finally {
if (previousFiber) {
setCurrentDebugFiberInDEV(fiber);
} else {
resetCurrentDebugFiberInDEV();
}
}
}
}
}

View File

@ -9,8 +9,10 @@
import type {Fiber} from './ReactInternalTypes';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import getComponentName from 'shared/getComponentName';
import {StrictMode} from './ReactTypeOfMode';
@ -336,18 +338,20 @@ if (__DEV__) {
});
const sortedNames = setToSortedString(uniqueNames);
const firstComponentStack = getStackByFiberInDevAndProd(firstFiber);
console.error(
'Legacy context API has been detected within a strict-mode tree.' +
'\n\nThe old API will be supported in all 16.x releases, but applications ' +
'using it should migrate to the new version.' +
'\n\nPlease update the following components: %s' +
'\n\nLearn more about this warning here: https://fb.me/react-legacy-context' +
'%s',
sortedNames,
firstComponentStack,
);
try {
setCurrentDebugFiberInDEV(firstFiber);
console.error(
'Legacy context API has been detected within a strict-mode tree.' +
'\n\nThe old API will be supported in all 16.x releases, but applications ' +
'using it should migrate to the new version.' +
'\n\nPlease update the following components: %s' +
'\n\nLearn more about this warning here: https://fb.me/react-legacy-context',
sortedNames,
);
} finally {
resetCurrentDebugFiberInDEV();
}
},
);
};

View File

@ -9,8 +9,10 @@
import type {Fiber} from './ReactInternalTypes';
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
import {
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import getComponentName from 'shared/getComponentName';
import {StrictMode} from './ReactTypeOfMode';
@ -336,18 +338,20 @@ if (__DEV__) {
});
const sortedNames = setToSortedString(uniqueNames);
const firstComponentStack = getStackByFiberInDevAndProd(firstFiber);
console.error(
'Legacy context API has been detected within a strict-mode tree.' +
'\n\nThe old API will be supported in all 16.x releases, but applications ' +
'using it should migrate to the new version.' +
'\n\nPlease update the following components: %s' +
'\n\nLearn more about this warning here: https://fb.me/react-legacy-context' +
'%s',
sortedNames,
firstComponentStack,
);
try {
setCurrentDebugFiberInDEV(firstFiber);
console.error(
'Legacy context API has been detected within a strict-mode tree.' +
'\n\nThe old API will be supported in all 16.x releases, but applications ' +
'using it should migrate to the new version.' +
'\n\nPlease update the following components: %s' +
'\n\nLearn more about this warning here: https://fb.me/react-legacy-context',
sortedNames,
);
} finally {
resetCurrentDebugFiberInDEV();
}
},
);
};

View File

@ -17,7 +17,6 @@ import {
} from 'shared/ReactSymbols';
import {isValidElement, cloneAndReplaceKey} from './ReactElement';
import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
const SEPARATOR = '.';
const SUBSEPARATOR = ':';
@ -190,21 +189,15 @@ function mapIntoArray(
);
}
} else if (type === 'object') {
let addendum = '';
if (__DEV__) {
addendum =
' If you meant to render a collection of children, use an array ' +
'instead.' +
ReactDebugCurrentFrame.getStackAddendum();
}
const childrenString = '' + (children: any);
invariant(
false,
'Objects are not valid as a React child (found: %s).%s',
'Objects are not valid as a React child (found: %s). ' +
'If you meant to render a collection of children, use an array ' +
'instead.',
childrenString === '[object Object]'
? 'object with keys {' + Object.keys((children: any)).join(', ') + '}'
: childrenString,
addendum,
);
}
}

View File

@ -29,19 +29,11 @@ function printWarning(level, format, args) {
// When changing this logic, you might want to also
// update consoleWithStackDev.www.js as well.
if (__DEV__) {
const hasExistingStack =
args.length > 0 &&
typeof args[args.length - 1] === 'string' &&
args[args.length - 1].indexOf('\n in') === 0;
if (!hasExistingStack) {
const ReactDebugCurrentFrame =
ReactSharedInternals.ReactDebugCurrentFrame;
const stack = ReactDebugCurrentFrame.getStackAddendum();
if (stack !== '') {
format += '%s';
args = args.concat([stack]);
}
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
const stack = ReactDebugCurrentFrame.getStackAddendum();
if (stack !== '') {
format += '%s';
args = args.concat([stack]);
}
const argsWithFormat = args.map(item => '' + item);

View File

@ -22,24 +22,17 @@ export function error(format, ...args) {
function printWarning(level, format, args) {
if (__DEV__) {
const hasExistingStack =
args.length > 0 &&
typeof args[args.length - 1] === 'string' &&
args[args.length - 1].indexOf('\n in') === 0;
if (!hasExistingStack) {
const React = require('react');
const ReactSharedInternals =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
// Defensive in case this is fired before React is initialized.
if (ReactSharedInternals != null) {
const ReactDebugCurrentFrame =
ReactSharedInternals.ReactDebugCurrentFrame;
const stack = ReactDebugCurrentFrame.getStackAddendum();
if (stack !== '') {
format += '%s';
args.push(stack);
}
const React = require('react');
const ReactSharedInternals =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
// Defensive in case this is fired before React is initialized.
if (ReactSharedInternals != null) {
const ReactDebugCurrentFrame =
ReactSharedInternals.ReactDebugCurrentFrame;
const stack = ReactDebugCurrentFrame.getStackAddendum();
if (stack !== '') {
format += '%s';
args.push(stack);
}
}
// TODO: don't ignore level and pass it down somewhere too.

View File

@ -30,7 +30,7 @@
"28": "Transaction.closeAll(): Cannot close transaction when none are open.",
"29": "accumulate(...): Accumulated items must be not be null or undefined.",
"30": "accumulateInto(...): Accumulated items must not be null or undefined.",
"31": "Objects are not valid as a React child (found: %s).%s",
"31": "Objects are not valid as a React child (found: %s). If you meant to render a collection of children, use an array instead.",
"32": "Unable to find element with ID %s.",
"33": "getNodeFromInstance: Invalid argument.",
"34": "React DOM tree root should always have a node reference.",
@ -58,10 +58,10 @@
"56": "dangerouslyReplaceNodeWithMarkup(...): Cannot render markup in a worker thread. Make sure `window` and `document` are available globally before requiring React when unit testing or use ReactDOMServer.renderToString() for server rendering.",
"57": "dangerouslyReplaceNodeWithMarkup(...): Missing markup.",
"58": "dangerouslyReplaceNodeWithMarkup(...): Cannot replace markup of the <html> node. This is because browser quirks make this unreliable and/or slow. If you want to render to the root you must use server rendering. See ReactDOMServer.renderToString().",
"59": "%s is a void element tag and must not have `children` or use `props.dangerouslySetInnerHTML`.%s",
"59": "%s is a void element tag and must not have `children` or use `props.dangerouslySetInnerHTML`.",
"60": "Can only set one of `children` or `props.dangerouslySetInnerHTML`.",
"61": "`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. Please visit https://fb.me/react-invariant-dangerously-set-inner-html for more information.",
"62": "The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.%s",
"62": "The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.",
"63": "Must be mounted to trap events",
"64": "trapBubbledEvent(...): Requires node to be rendered.",
"65": "Invalid tag: %s",
@ -136,7 +136,7 @@
"134": "Touch data should have been recorded on start",
"135": "Cannot find single active touch",
"136": "Attempted to update component `%s` that has already been unmounted (or failed to mount).",
"137": "%s is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.%s",
"137": "%s is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.",
"138": "Touch object is missing identifier.",
"139": "ReactTestRenderer: .update() can't be called after unmount.",
"140": "Expected hook events to fire for the child before its parent includes it in onSetChildren().",
@ -321,7 +321,7 @@
"320": "Expected ReactFiberErrorDialog.showErrorDialog to be a function.",
"321": "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.",
"322": "forwardRef requires a render function but was given %s.",
"323": "React has blocked a javascript: URL as a security precaution.%s",
"323": "React has blocked a javascript: URL as a security precaution.",
"324": "An event responder context was used outside of an event cycle. Use context.setTimeout() to use asynchronous responder context outside of event cycle .",
"325": "addRootEventTypes() found a duplicate root event type of \"%s\". This might be because the event type exists in the event responder \"rootEventTypes\" array or because of a previous addRootEventTypes() using this root event type.",
"326": "Expected a valid priority level",

View File

@ -65,7 +65,8 @@ const createMatcherFor = (consoleMethod, matcherName) =>
let caughtError;
const isLikelyAComponentStack = message =>
typeof message === 'string' && message.includes('\n in ');
typeof message === 'string' &&
(message.includes('\n in ') || message.includes('\n at '));
const consoleSpy = (format, ...args) => {
// Ignore uncaught errors reported by jsdom