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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix componentWillReceiveProps when calling setState/forceUpdate #2186

Merged
merged 1 commit into from Dec 16, 2019

Conversation

jamesb3ll
Copy link
Contributor

Fixes #2185

componentWillReceiveProps doesn't get called when calling setState or forceUpdate and receiving props.

componentWillReceiveProps never gets called as this.setState/forceUpdate will set component._force = false/true, so _force will not be null or undefined as expected in diff/index.js:

if (
  newType.getDerivedStateFromProps == null &&
  c._force == null &&
  c.componentWillReceiveProps != null
) {
  c.componentWillReceiveProps(newProps, cctx);
}

Instead of using the _force flag, I've decided to check the equality of oldProps and newProps to check whether props have changed. I've added a test for this case too. It would be great to know if I'm missing an edge case with this fix?

This fix also drops the size by -6B 馃帀

@coveralls
Copy link

coveralls commented Dec 16, 2019

Coverage Status

Coverage remained the same at 100.0% when pulling afb335f on jamesb3ll:fix-cWRP into 59c036c on preactjs:master.

Copy link
Member

@JoviDeCroock JoviDeCroock left a comment

Choose a reason for hiding this comment

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

I think this is safe to do, do note that oldProps and newProps will never be referentially equal due to https://github.com/preactjs/preact/blob/master/src/create-element.js#L12

@marvinhagemeister
Copy link
Member

@JoviDeCroock This is not always true. For the component where setState/forceUpdate will be called on, the props will be referentially equal. The whole subtree will have newly created props objects due to the line you linked to tough.

@jamesb3ll
Copy link
Contributor Author

jamesb3ll commented Dec 16, 2019

Thanks @JoviDeCroock 馃檪I was also confused with that, but with the debugger I saw that the parent component (setting state or forceUpdate) kept the same equality for newProps and oldProps and that way we can avoid that component calling componentWillReceiveProps

I've been playing around in this codesandbox here: https://codesandbox.io/s/preact-x-cwrp-b1inz

Edit: Exactly, thanks @marvinhagemeister 馃槂

@@ -183,6 +183,59 @@ describe('Lifecycle methods', () => {
expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
});

it('should be called when rerender with new props from parent even with setState/forceUpdate in child', () => {
Copy link
Member

Choose a reason for hiding this comment

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

This test is so good 馃槏

Copy link
Member

@marvinhagemeister marvinhagemeister left a comment

Choose a reason for hiding this comment

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

This PR is awesome! The more I look at it, the more I'm convinced that the solution here is pretty genius!

The issue arises because both the Inner and Outer components are enqueued as part of the same commit. With each queued component we mark the update type with the _force flag that is stored on the component class. Trouble arises because we expected the queued component to always be the root of the subtree it updates, never as one of the children of an already queued component. Thus our logic broke, because we checked each component for the _force flag during diff, instead of just the one at the root of the update.

This solution here is a stroke of genius, because it leverages the fact that, the props of a root component of any sub-tree will have referentially equal props. Every other vnode below that will have a newly created object for props like Jovi mentioned. Therefore checking for props equality is the smallest and easiest way to check for a root and assert if cWRP should be called 馃帀

Love it, thank you so much for the PR 馃憤 馃挴

@marvinhagemeister marvinhagemeister merged commit 287e86e into preactjs:master Dec 16, 2019
@marvinhagemeister
Copy link
Member

FYI: We'll likely publish a new release in a couple of hours 馃憤

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

componentWillReceiveProps not being called after setState/forceUpdate
4 participants