diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index 3ad78e2a7926..f08189c3ef01 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -856,7 +856,7 @@ function updateClassComponent(
);
if (__DEV__) {
const inst = workInProgress.stateNode;
- if (inst.props !== nextProps) {
+ if (shouldUpdate && inst.props !== nextProps) {
if (!didWarnAboutReassigningProps) {
console.error(
'It looks like %s is reassigning its own `this.props` while rendering. ' +
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js
index 7b9be3544e71..a688b891fa51 100644
--- a/packages/react-reconciler/src/ReactFiberClassComponent.js
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.js
@@ -997,11 +997,13 @@ function updateClassInstance(
cloneUpdateQueue(current, workInProgress);
- const oldProps = workInProgress.memoizedProps;
- instance.props =
+ const unresolvedOldProps = workInProgress.memoizedProps;
+ const oldProps =
workInProgress.type === workInProgress.elementType
- ? oldProps
- : resolveDefaultProps(workInProgress.type, oldProps);
+ ? unresolvedOldProps
+ : resolveDefaultProps(workInProgress.type, unresolvedOldProps);
+ instance.props = oldProps;
+ const unresolvedNewProps = workInProgress.pendingProps;
const oldContext = instance.context;
const contextType = ctor.contextType;
@@ -1029,7 +1031,10 @@ function updateClassInstance(
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function')
) {
- if (oldProps !== newProps || oldContext !== nextContext) {
+ if (
+ unresolvedOldProps !== unresolvedNewProps ||
+ oldContext !== nextContext
+ ) {
callComponentWillReceiveProps(
workInProgress,
instance,
@@ -1047,7 +1052,7 @@ function updateClassInstance(
newState = workInProgress.memoizedState;
if (
- oldProps === newProps &&
+ unresolvedOldProps === unresolvedNewProps &&
oldState === newState &&
!hasContextChanged() &&
!checkHasForceUpdateAfterProcessing()
@@ -1056,7 +1061,7 @@ function updateClassInstance(
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
- oldProps !== current.memoizedProps ||
+ unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
@@ -1064,7 +1069,7 @@ function updateClassInstance(
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
if (
- oldProps !== current.memoizedProps ||
+ unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Snapshot;
@@ -1121,7 +1126,7 @@ function updateClassInstance(
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
- oldProps !== current.memoizedProps ||
+ unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
@@ -1129,7 +1134,7 @@ function updateClassInstance(
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
if (
- oldProps !== current.memoizedProps ||
+ unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Snapshot;
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index d542173fce30..33533a754586 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -343,6 +343,97 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('SiblingB');
});
+ it('resolves defaultProps without breaking bailout due to unchanged props and state, #17151', async () => {
+ class LazyImpl extends React.Component {
+ static defaultProps = {value: 0};
+
+ render() {
+ const text = `${this.props.label}: ${this.props.value}`;
+ return ;
+ }
+ }
+
+ const Lazy = lazy(() => fakeImport(LazyImpl));
+
+ const instance1 = React.createRef(null);
+ const instance2 = React.createRef(null);
+
+ const root = ReactTestRenderer.create(
+ <>
+
+ }>
+
+
+ >,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+ expect(Scheduler).toFlushAndYield(['Not lazy: 0', 'Loading...']);
+ expect(root).not.toMatchRenderedOutput('Not lazy: 0Lazy: 0');
+
+ await Promise.resolve();
+
+ expect(Scheduler).toFlushAndYield(['Lazy: 0']);
+ expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
+
+ // Should bailout due to unchanged props and state
+ instance1.current.setState(null);
+ expect(Scheduler).toFlushAndYield([]);
+ expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
+
+ // Should bailout due to unchanged props and state
+ instance2.current.setState(null);
+ expect(Scheduler).toFlushAndYield([]);
+ expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
+ });
+
+ it('resolves defaultProps without breaking bailout in PureComponent, #17151', async () => {
+ class LazyImpl extends React.PureComponent {
+ static defaultProps = {value: 0};
+ state = {};
+
+ render() {
+ const text = `${this.props.label}: ${this.props.value}`;
+ return ;
+ }
+ }
+
+ const Lazy = lazy(() => fakeImport(LazyImpl));
+
+ const instance1 = React.createRef(null);
+ const instance2 = React.createRef(null);
+
+ const root = ReactTestRenderer.create(
+ <>
+
+ }>
+
+
+ >,
+ {
+ unstable_isConcurrent: true,
+ },
+ );
+ expect(Scheduler).toFlushAndYield(['Not lazy: 0', 'Loading...']);
+ expect(root).not.toMatchRenderedOutput('Not lazy: 0Lazy: 0');
+
+ await Promise.resolve();
+
+ expect(Scheduler).toFlushAndYield(['Lazy: 0']);
+ expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
+
+ // Should bailout due to shallow equal props and state
+ instance1.current.setState({});
+ expect(Scheduler).toFlushAndYield([]);
+ expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
+
+ // Should bailout due to shallow equal props and state
+ instance2.current.setState({});
+ expect(Scheduler).toFlushAndYield([]);
+ expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
+ });
+
it('sets defaultProps for modern lifecycles', async () => {
class C extends React.Component {
static defaultProps = {text: 'A'};