Warn about reassigning this.props (#14277)

* Warn about reassigning this.props

* Improve the warning

* Don't show the spammy bug warning if we suspect it's a component bug
This commit is contained in:
Dan Abramov 2018-11-20 16:40:01 +00:00 committed by GitHub
parent 327cf0ee33
commit a9fdf8a326
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 21 deletions

View File

@ -1743,6 +1743,25 @@ describe('ReactCompositeComponent', () => {
);
});
it('should warn about reassigning this.props while rendering', () => {
class Bad extends React.Component {
componentDidMount() {}
componentDidUpdate() {}
render() {
this.props = {...this.props};
return null;
}
}
const container = document.createElement('div');
expect(() => {
ReactDOM.render(<Bad />, container);
}).toWarnDev(
'It looks like Bad is reassigning its own `this.props` while rendering. ' +
'This is not supported and can lead to confusing bugs.',
);
});
it('should return error if render is not defined', () => {
class RenderTestUndefinedRender extends React.Component {}

View File

@ -130,12 +130,14 @@ let didWarnAboutBadClass;
let didWarnAboutContextTypeOnFunctionComponent;
let didWarnAboutGetDerivedStateOnFunctionComponent;
let didWarnAboutFunctionRefs;
export let didWarnAboutReassigningProps;
if (__DEV__) {
didWarnAboutBadClass = {};
didWarnAboutContextTypeOnFunctionComponent = {};
didWarnAboutGetDerivedStateOnFunctionComponent = {};
didWarnAboutFunctionRefs = {};
didWarnAboutReassigningProps = false;
}
export function reconcileChildren(
@ -495,7 +497,7 @@ function updateClassComponent(
renderExpirationTime,
);
}
return finishClassComponent(
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
@ -503,6 +505,19 @@ function updateClassComponent(
hasContext,
renderExpirationTime,
);
if (__DEV__) {
let inst = workInProgress.stateNode;
if (inst.props !== nextProps) {
warning(
didWarnAboutReassigningProps,
'It looks like %s is reassigning its own `this.props` while rendering. ' +
'This is not supported and can lead to confusing bugs.',
getComponentName(workInProgress.type) || 'a component',
);
didWarnAboutReassigningProps = true;
}
}
return nextUnitOfWork;
}
function finishClassComponent(

View File

@ -99,6 +99,7 @@ import {
UnmountPassive,
MountPassive,
} from './ReactHookEffectTags';
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
if (__DEV__) {
@ -234,18 +235,27 @@ function commitBeforeMutationLifeCycles(
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (finishedWork.type === finishedWork.elementType) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
warning(
instance.props === finishedWork.memoizedProps,
'Expected instance props to match memoized props before ' +
'getSnapshotBeforeUpdate. This is likely due to a bug in React. ' +
'Expected %s props to match memoized props before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
warning(
instance.state === finishedWork.memoizedState,
'Expected instance state to match memoized state before ' +
'getSnapshotBeforeUpdate. This is likely due to a bug in React. ' +
'Expected %s state to match memoized state before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
@ -370,18 +380,27 @@ function commitLifeCycles(
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (finishedWork.type === finishedWork.elementType) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
warning(
instance.props === finishedWork.memoizedProps,
'Expected instance props to match memoized props before ' +
'componentDidMount. This is likely due to a bug in React. ' +
'Expected %s props to match memoized props before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
warning(
instance.state === finishedWork.memoizedState,
'Expected instance state to match memoized state before ' +
'componentDidMount. This is likely due to a bug in React. ' +
'Expected %s state to match memoized state before ' +
'componentDidMount. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
@ -398,18 +417,27 @@ function commitLifeCycles(
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (finishedWork.type === finishedWork.elementType) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
warning(
instance.props === finishedWork.memoizedProps,
'Expected instance props to match memoized props before ' +
'componentDidUpdate. This is likely due to a bug in React. ' +
'Expected %s props to match memoized props before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
warning(
instance.state === finishedWork.memoizedState,
'Expected instance state to match memoized state before ' +
'componentDidUpdate. This is likely due to a bug in React. ' +
'Expected %s state to match memoized state before ' +
'componentDidUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
@ -424,18 +452,27 @@ function commitLifeCycles(
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
if (__DEV__) {
if (finishedWork.type === finishedWork.elementType) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
warning(
instance.props === finishedWork.memoizedProps,
'Expected instance props to match memoized props before ' +
'processing the update queue. This is likely due to a bug in React. ' +
'Expected %s props to match memoized props before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
warning(
instance.state === finishedWork.memoizedState,
'Expected instance state to match memoized state before ' +
'processing the update queue. This is likely due to a bug in React. ' +
'Expected %s state to match memoized state before ' +
'processing the update queue. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}