Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bailout broken in lazy components due to default props resolving #18491

Closed
wants to merge 2 commits into from
Closed

Conversation

jddxf
Copy link
Contributor

@jddxf jddxf commented Apr 4, 2020

Fix #17151

We should never compare unresolved props with resolved props. Since comparing resolved props by reference doesn't make sense, we use unresolved props in that case. Otherwise, resolved props are used.

We should never compare unresolved props with resolved props. Since comparing
resolved props by reference doesn't make sense, we use unresolved props in that
case. Otherwise, resolved props are used.
@codesandbox-ci
Copy link

codesandbox-ci bot commented Apr 4, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 92c15f2:

Sandbox Source
admiring-blackwell-h7cmt Configuration
strange-glitter-e7gwb Issue #17151

@sizebot
Copy link

sizebot commented Apr 4, 2020

No significant bundle size changes to report.

Size changes (stable)

Generated by 🚫 dangerJS against 92c15f2

@sizebot
Copy link

sizebot commented Apr 4, 2020

No significant bundle size changes to report.

Size changes (experimental)

Generated by 🚫 dangerJS against 92c15f2

@bl00mber
Copy link
Contributor

bl00mber commented Apr 4, 2020

@jddxf you probably may want to change the way you pull updated changes form react original repo to your fork because when you pull them out you leave a lot of references in all the other pull requests!!!

Screenshot from 2020-04-05 1

Screenshot from 2020-04-05 2

oldState !== current.memoizedState
) {
workInProgress.effectTag |= Snapshot;
}
}
instance.props = workInProgress.memoizedProps = newProps;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise, React would warn.

if (inst.props !== nextProps) {
if (!didWarnAboutReassigningProps) {
console.error(
'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;
}

This also mirrors the logic at the end of the same function.

// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.
workInProgress.memoizedProps = newProps;
workInProgress.memoizedState = newState;
}
// Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;
return shouldUpdate;

This was not needed before because it was already guaranteed by the condition in the branch.

@jddxf
Copy link
Contributor Author

jddxf commented Apr 5, 2020

@bl00mber Sorry for that. I did force pushed the right commits but that didn't work. I had to delete the fork and it seems ok now.

@@ -997,11 +997,13 @@ function updateClassInstance(

cloneUpdateQueue(current, workInProgress);

const oldProps = workInProgress.memoizedProps;
instance.props =
const unresolvedOldProps = workInProgress.memoizedProps;
Copy link
Collaborator

@gaearon gaearon Apr 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says they're unresolved. But later we do this:

instance.props = workInProgress.memoizedProps = newProps;

So next time we would have resolved props in memoizedProps? I don't quite follow the logic.

Is there a way we could make it consistent? So that memoizedProps would always be unresolved, for example. Would that work?

Copy link
Contributor Author

@jddxf jddxf Apr 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we reset workInProgress.memoizedProps to workInProgress.pendingProps right after beginWork, workInProgress.memoizedProps is always unresolved at the start of beginWork.

unitOfWork.memoizedProps = unitOfWork.pendingProps;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then can we assign instance.props only? Is there a reason for workInPorgress.memoizedProps assignment? Sorry I'm being dense, it's been a while since I looked a tthis.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind. But unfortunately we have to keep their values in sync. Here is the reason.

Copy link
Collaborator

@gaearon gaearon Apr 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't think it makes sense to assign either of them.

In the sCU branch, instance.props reassignment makes sense because the passed props actually did change. We want the most recent values inside of them to be accessible to the lifecycle methods, even if sCU "lies" about them not having changed.

In this branch, we've verified props are ===. So the only reason they "changed" is because we resolved default props. That's not a compelling reason to give our components a different props object.

As you noted, memoizedProps gets reset anyway. So it's confusing to assign it here if we know that's gonna happen.

I think we should fix whatever causes the warning instead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you change the warning condition to if (shouldUpdate && inst.props !== nextProps) { then the warning won't fire. It will also make sense because the render wasn't called — so the warning message would have been misleading anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instance.props = workInProgress.memoizedProps = newProps;

This only gives our components a different props object when we've actually resolved default props. But in that case, components are already given a new props object.

instance.props =
workInProgress.type === workInProgress.elementType
? oldProps
: resolveDefaultProps(workInProgress.type, oldProps);

I think it's ok to remove this assignment if keeping workInProgress.memoizedProps and instance.props stay the same until beginWork finishes is not a requirement. And that seems true now. So let's change the warning condition instead.

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the new assignment in the bailout case and change the warning condition instead to only fire when shouldUpdate is true.

@jddxf
Copy link
Contributor Author

jddxf commented Apr 8, 2020

I cannot update this PR now due to refork. Closed in favour of #18539.

@jddxf jddxf closed this Apr 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bailing out doesn't work properly in lazy components with default props
5 participants