* Deduplicated many warnings (#11140) * Deduplicated the following warnings: 1. Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op 2. %s.componentWillReceiveProps(): Assigning directly to this.state is deprecated (except inside a component's constructor). Use setState instead.' 3. An update (setState, replaceState, or forceUpdate) was scheduled from inside an update function. Update functions should be pure, with zero side-effects. Consider using componentDidUpdate or a callback. 4. setState(...): Cannot call setState() inside getChildContext() * Code review changes made for #11140 * Minor style fix * Test deduplication for noop updates in server renderer * Test deduplication for cWRP warning * Test deduplication for cWM setState warning * Test deduplication for unnmounted setState warning * Fix existing Flow typing * Test deduplication for invalid updates * Test deduplication of update-in-updater warning
This commit is contained in:
parent
7f10fae4c1
commit
3f1f3dc12e
|
@ -221,6 +221,10 @@ describe('ReactComponentLifeCycle', () => {
|
||||||
'unmounted component. This is a no-op.\n\nPlease check the code for the ' +
|
'unmounted component. This is a no-op.\n\nPlease check the code for the ' +
|
||||||
'StatefulComponent component.',
|
'StatefulComponent component.',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check deduplication
|
||||||
|
ReactTestUtils.renderIntoDocument(<StatefulComponent />);
|
||||||
|
expectDev(console.error.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly determine if a component is mounted', () => {
|
it('should correctly determine if a component is mounted', () => {
|
||||||
|
|
|
@ -252,6 +252,9 @@ describe('ReactCompositeComponent', () => {
|
||||||
'component. This is a no-op.\n\nPlease check the code for the ' +
|
'component. This is a no-op.\n\nPlease check the code for the ' +
|
||||||
'Component component.',
|
'Component component.',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
instance.forceUpdate();
|
||||||
|
expectDev(console.error.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn about `setState` on unmounted components', () => {
|
it('should warn about `setState` on unmounted components', () => {
|
||||||
|
@ -391,6 +394,11 @@ describe('ReactCompositeComponent', () => {
|
||||||
expect(instance).toBe(instance2);
|
expect(instance).toBe(instance2);
|
||||||
expect(renderedState).toBe(1);
|
expect(renderedState).toBe(1);
|
||||||
expect(instance2.state.value).toBe(1);
|
expect(instance2.state.value).toBe(1);
|
||||||
|
|
||||||
|
// Test deduplication
|
||||||
|
ReactDOM.unmountComponentAtNode(container);
|
||||||
|
ReactDOM.render(<Component prop={123} />, container);
|
||||||
|
expectDev(console.error.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn about `setState` in getChildContext', () => {
|
it('should warn about `setState` in getChildContext', () => {
|
||||||
|
@ -424,6 +432,11 @@ describe('ReactCompositeComponent', () => {
|
||||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||||
'Warning: setState(...): Cannot call setState() inside getChildContext()',
|
'Warning: setState(...): Cannot call setState() inside getChildContext()',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Test deduplication
|
||||||
|
ReactDOM.unmountComponentAtNode(container);
|
||||||
|
ReactDOM.render(<Component />, container);
|
||||||
|
expectDev(console.error.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cleanup even if render() fatals', () => {
|
it('should cleanup even if render() fatals', () => {
|
||||||
|
|
|
@ -421,6 +421,10 @@ describe('ReactCompositeComponent-state', () => {
|
||||||
"this.state is deprecated (except inside a component's constructor). " +
|
"this.state is deprecated (except inside a component's constructor). " +
|
||||||
'Use setState instead.',
|
'Use setState instead.',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check deduplication
|
||||||
|
ReactDOM.render(<Test />, container);
|
||||||
|
expect(console.error.calls.count()).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should treat assigning to this.state inside cWM as a replaceState, with a warning', () => {
|
it('should treat assigning to this.state inside cWM as a replaceState, with a warning', () => {
|
||||||
|
|
|
@ -656,8 +656,11 @@ describe('ReactDOMServer', () => {
|
||||||
' This usually means you called setState() outside componentWillMount() on the server.' +
|
' This usually means you called setState() outside componentWillMount() on the server.' +
|
||||||
' This is a no-op.\n\nPlease check the code for the Foo component.',
|
' This is a no-op.\n\nPlease check the code for the Foo component.',
|
||||||
);
|
);
|
||||||
|
|
||||||
var markup = ReactDOMServer.renderToStaticMarkup(<Foo />);
|
var markup = ReactDOMServer.renderToStaticMarkup(<Foo />);
|
||||||
expect(markup).toBe('<div>hello</div>');
|
expect(markup).toBe('<div>hello</div>');
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
|
expectDev(console.error.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns with a no-op when an async forceUpdate is triggered', () => {
|
it('warns with a no-op when an async forceUpdate is triggered', () => {
|
||||||
|
|
|
@ -118,6 +118,7 @@ var didWarnDefaultChecked = false;
|
||||||
var didWarnDefaultSelectValue = false;
|
var didWarnDefaultSelectValue = false;
|
||||||
var didWarnDefaultTextareaValue = false;
|
var didWarnDefaultTextareaValue = false;
|
||||||
var didWarnInvalidOptionChildren = false;
|
var didWarnInvalidOptionChildren = false;
|
||||||
|
var didWarnAboutNoopUpdateForComponent = {};
|
||||||
var valuePropNames = ['value', 'defaultValue'];
|
var valuePropNames = ['value', 'defaultValue'];
|
||||||
var newlineEatingTags = {
|
var newlineEatingTags = {
|
||||||
listing: true,
|
listing: true,
|
||||||
|
@ -181,6 +182,13 @@ function warnNoop(
|
||||||
) {
|
) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
var constructor = publicInstance.constructor;
|
var constructor = publicInstance.constructor;
|
||||||
|
const componentName =
|
||||||
|
(constructor && getComponentName(constructor)) || 'ReactClass';
|
||||||
|
const warningKey = `${componentName}.${callerName}`;
|
||||||
|
if (didWarnAboutNoopUpdateForComponent[warningKey]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
warning(
|
warning(
|
||||||
false,
|
false,
|
||||||
'%s(...): Can only update a mounting component. ' +
|
'%s(...): Can only update a mounting component. ' +
|
||||||
|
@ -188,8 +196,9 @@ function warnNoop(
|
||||||
'This is a no-op.\n\nPlease check the code for the %s component.',
|
'This is a no-op.\n\nPlease check the code for the %s component.',
|
||||||
callerName,
|
callerName,
|
||||||
callerName,
|
callerName,
|
||||||
(constructor && getComponentName(constructor)) || 'ReactClass',
|
componentName,
|
||||||
);
|
);
|
||||||
|
didWarnAboutNoopUpdateForComponent[warningKey] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ if (__DEV__) {
|
||||||
var warning = require('fbjs/lib/warning');
|
var warning = require('fbjs/lib/warning');
|
||||||
|
|
||||||
var {startPhaseTimer, stopPhaseTimer} = require('./ReactDebugFiberPerf');
|
var {startPhaseTimer, stopPhaseTimer} = require('./ReactDebugFiberPerf');
|
||||||
|
var didWarnAboutStateAssignmentForComponent = {};
|
||||||
|
|
||||||
var warnOnInvalidCallback = function(callback: mixed, callerName: string) {
|
var warnOnInvalidCallback = function(callback: mixed, callerName: string) {
|
||||||
warning(
|
warning(
|
||||||
|
@ -393,13 +394,17 @@ module.exports = function(
|
||||||
|
|
||||||
if (instance.state !== oldState) {
|
if (instance.state !== oldState) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
warning(
|
const componentName = getComponentName(workInProgress) || 'Component';
|
||||||
false,
|
if (!didWarnAboutStateAssignmentForComponent[componentName]) {
|
||||||
'%s.componentWillReceiveProps(): Assigning directly to ' +
|
warning(
|
||||||
"this.state is deprecated (except inside a component's " +
|
false,
|
||||||
'constructor). Use setState instead.',
|
'%s.componentWillReceiveProps(): Assigning directly to ' +
|
||||||
getComponentName(workInProgress),
|
"this.state is deprecated (except inside a component's " +
|
||||||
);
|
'constructor). Use setState instead.',
|
||||||
|
componentName,
|
||||||
|
);
|
||||||
|
didWarnAboutStateAssignmentForComponent[componentName] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updater.enqueueReplaceState(instance, instance.state, null);
|
updater.enqueueReplaceState(instance, instance.state, null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,34 +101,41 @@ if (__DEV__) {
|
||||||
} = require('./ReactDebugFiberPerf');
|
} = require('./ReactDebugFiberPerf');
|
||||||
|
|
||||||
var didWarnAboutStateTransition = false;
|
var didWarnAboutStateTransition = false;
|
||||||
|
var didWarnSetStateChildContext = false;
|
||||||
|
var didWarnStateUpdateForUnmountedComponent = {};
|
||||||
|
|
||||||
var warnAboutUpdateOnUnmounted = function(
|
var warnAboutUpdateOnUnmounted = function(fiber: Fiber) {
|
||||||
instance: React$ComponentType<any>,
|
const componentName = getComponentName(fiber) || 'ReactClass';
|
||||||
) {
|
if (didWarnStateUpdateForUnmountedComponent[componentName]) {
|
||||||
const ctor = instance.constructor;
|
return;
|
||||||
|
}
|
||||||
warning(
|
warning(
|
||||||
false,
|
false,
|
||||||
'Can only update a mounted or mounting component. This usually means ' +
|
'Can only update a mounted or mounting ' +
|
||||||
'you called setState, replaceState, or forceUpdate on an unmounted ' +
|
'component. This usually means you called setState, replaceState, ' +
|
||||||
'component. This is a no-op.\n\nPlease check the code for the ' +
|
'or forceUpdate on an unmounted component. This is a no-op.\n\nPlease ' +
|
||||||
'%s component.',
|
'check the code for the %s component.',
|
||||||
(ctor && (ctor.displayName || ctor.name)) || 'ReactClass',
|
componentName,
|
||||||
);
|
);
|
||||||
|
didWarnStateUpdateForUnmountedComponent[componentName] = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
var warnAboutInvalidUpdates = function(instance: React$ComponentType<any>) {
|
var warnAboutInvalidUpdates = function(instance: React$Component<any>) {
|
||||||
switch (ReactDebugCurrentFiber.phase) {
|
switch (ReactDebugCurrentFiber.phase) {
|
||||||
case 'getChildContext':
|
case 'getChildContext':
|
||||||
|
if (didWarnSetStateChildContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
warning(
|
warning(
|
||||||
false,
|
false,
|
||||||
'setState(...): Cannot call setState() inside getChildContext()',
|
'setState(...): Cannot call setState() inside getChildContext()',
|
||||||
);
|
);
|
||||||
|
didWarnSetStateChildContext = true;
|
||||||
break;
|
break;
|
||||||
case 'render':
|
case 'render':
|
||||||
if (didWarnAboutStateTransition) {
|
if (didWarnAboutStateTransition) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
didWarnAboutStateTransition = true;
|
|
||||||
warning(
|
warning(
|
||||||
false,
|
false,
|
||||||
'Cannot update during an existing state transition (such as within ' +
|
'Cannot update during an existing state transition (such as within ' +
|
||||||
|
@ -136,6 +143,7 @@ if (__DEV__) {
|
||||||
'be a pure function of props and state; constructor side-effects are ' +
|
'be a pure function of props and state; constructor side-effects are ' +
|
||||||
'an anti-pattern, but can be moved to `componentWillMount`.',
|
'an anti-pattern, but can be moved to `componentWillMount`.',
|
||||||
);
|
);
|
||||||
|
didWarnAboutStateTransition = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1229,7 +1237,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
|
||||||
} else {
|
} else {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
if (!isErrorRecovery && fiber.tag === ClassComponent) {
|
if (!isErrorRecovery && fiber.tag === ClassComponent) {
|
||||||
warnAboutUpdateOnUnmounted(fiber.stateNode);
|
warnAboutUpdateOnUnmounted(fiber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -20,6 +20,7 @@ const {NoWork} = require('./ReactFiberExpirationTime');
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
var warning = require('fbjs/lib/warning');
|
var warning = require('fbjs/lib/warning');
|
||||||
|
var didWarnUpdateInsideUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PartialState<State, Props> =
|
type PartialState<State, Props> =
|
||||||
|
@ -132,7 +133,10 @@ function insertUpdateIntoFiber<State>(
|
||||||
|
|
||||||
// Warn if an update is scheduled from inside an updater function.
|
// Warn if an update is scheduled from inside an updater function.
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
if (queue1.isProcessing || (queue2 !== null && queue2.isProcessing)) {
|
if (
|
||||||
|
(queue1.isProcessing || (queue2 !== null && queue2.isProcessing)) &&
|
||||||
|
!didWarnUpdateInsideUpdate
|
||||||
|
) {
|
||||||
warning(
|
warning(
|
||||||
false,
|
false,
|
||||||
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
|
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
|
||||||
|
@ -140,6 +144,7 @@ function insertUpdateIntoFiber<State>(
|
||||||
'with zero side-effects. Consider using componentDidUpdate or a ' +
|
'with zero side-effects. Consider using componentDidUpdate or a ' +
|
||||||
'callback.',
|
'callback.',
|
||||||
);
|
);
|
||||||
|
didWarnUpdateInsideUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -341,6 +341,19 @@ describe('ReactIncrementalUpdates', () => {
|
||||||
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
||||||
|
|
||||||
expectDev(console.error.calls.count()).toBe(1);
|
expectDev(console.error.calls.count()).toBe(1);
|
||||||
console.error.calls.reset();
|
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||||
|
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
|
||||||
|
'from inside an update function. Update functions should be pure, ' +
|
||||||
|
'with zero side-effects. Consider using componentDidUpdate or a ' +
|
||||||
|
'callback.',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test deduplication
|
||||||
|
instance.setState(function a() {
|
||||||
|
this.setState({a: 'a'});
|
||||||
|
return {b: 'b'};
|
||||||
|
});
|
||||||
|
ReactNoop.flush();
|
||||||
|
expectDev(console.error.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,11 +9,19 @@
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
var warning = require('fbjs/lib/warning');
|
var warning = require('fbjs/lib/warning');
|
||||||
|
var didWarnStateUpdateForUnmountedComponent = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function warnNoop(publicInstance, callerName) {
|
function warnNoop(publicInstance, callerName) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
var constructor = publicInstance.constructor;
|
var constructor = publicInstance.constructor;
|
||||||
|
const componentName =
|
||||||
|
(constructor && (constructor.displayName || constructor.name)) ||
|
||||||
|
'ReactClass';
|
||||||
|
const warningKey = `${componentName}.${callerName}`;
|
||||||
|
if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
warning(
|
warning(
|
||||||
false,
|
false,
|
||||||
'%s(...): Can only update a mounted or mounting component. ' +
|
'%s(...): Can only update a mounted or mounting component. ' +
|
||||||
|
@ -21,9 +29,9 @@ function warnNoop(publicInstance, callerName) {
|
||||||
'This is a no-op.\n\nPlease check the code for the %s component.',
|
'This is a no-op.\n\nPlease check the code for the %s component.',
|
||||||
callerName,
|
callerName,
|
||||||
callerName,
|
callerName,
|
||||||
(constructor && (constructor.displayName || constructor.name)) ||
|
componentName,
|
||||||
'ReactClass',
|
|
||||||
);
|
);
|
||||||
|
didWarnStateUpdateForUnmountedComponent[warningKey] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue