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
Add support for getDerivedStateFromProps #1094
Add support for getDerivedStateFromProps #1094
Conversation
src/vdom/component.js
Outdated
if (!component.base || mountAll) { | ||
if (component.constructor.getDerivedStateFromProps) { | ||
component.state = extend(component.state, component.constructor.getDerivedStateFromProps(props, component.state)); | ||
} else if (!component.base || mountAll) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we make getDerivedStateFromProps mutually exclusive to componentWillMount and componentWillReceiveProps for two reasons:
It saves 2 bytes! (okay not a real reason, but just thought I'd mention it :) )No longer true after commit 4150a25- React makes them mutually exclusive. And while Preact can differ from React, I think in this instance it might be better if follow their lead. Matching React's behavior prevents us from having to define, document, and maintain the interaction between getDerivedStateFromProps and the deprecated lifecycle methods.
Thoughts?
@@ -23,6 +23,206 @@ describe('Lifecycle methods', () => { | |||
}); | |||
|
|||
|
|||
describe('static getDerivedStateFromProps', () => { | |||
it('should set initial state with value returned from getDerivedStateFromProps', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(element.className).to.be.equal('foo bar'); | ||
}); | ||
|
||
it('should update initial state with value returned from getDerivedStateFromProps', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(element.className).to.equal('not-foo bar'); | ||
}); | ||
|
||
it('should update the instance\'s state with the value returned from getDerivedStateFromProps when props change', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/browser/lifecycle.js
Outdated
expect(Foo.prototype.componentDidUpdate).to.have.callCount(1); // verify update occurred | ||
}); | ||
|
||
it('should NOT be invoked on state only updates', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might change in a minor/patch version. Related: facebook/react#12600 (comment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the heads up! I'll take a look and see what we can do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to handle state-only updates
test/browser/lifecycle.js
Outdated
expect(Foo.getDerivedStateFromProps).to.have.been.calledOnce; | ||
}); | ||
|
||
it('should NOT modify state if null is returned', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}); | ||
|
||
// NOTE: Difference from React | ||
// React v16.3.2 warns if undefined if returned from getDerivedStateFromProps |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
// TODO: Consider if componentWillUpdate should still be called | ||
// Likely, componentWillUpdate should not be called only if getSnapshotBeforeUpdate is implemented | ||
it('should NOT invoke deprecated lifecycles (cWM/cWRP) if new static gDSFP is present', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andrewiggins Thanks for making a PR for this 🎉 I have to read a bit more on the backstory of I'm currently lying in bed with an illness, but will review this once my health is back up again 🎉 |
@marvinhagemeister Hope you are feeling a little better! And no worries. I appreciate you circling back. I'm anticipating there will be more discussion and planning around what to do and how to deliver new lifecycle methods before we ship this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Let's see if we can nudge that size at all, otherwise I am happy with this. Thanks for your hard work!
} | ||
else if (component.componentWillReceiveProps) { | ||
component.componentWillReceiveProps(props, context); | ||
if (typeof component.constructor.getDerivedStateFromProps === 'undefined') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be open to loosening this check if it can help offset the size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By loosening do you mean making the check less specific (i.e if (!component.constructor.getDerivedStateFromProps)
)? I just tried that change in commit 3f51408 and it actually increased the size by 2 bytes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andrewiggins I think he meant removing the if
-statement completely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, whoops. That makes sense. Yea, that would save about 4 bytes I believe.
src/vdom/component.js
Outdated
@@ -73,6 +75,11 @@ export function renderComponent(component, opts, mountAll, isChild) { | |||
skip = false, | |||
rendered, inst, cbase; | |||
|
|||
let getDerivedStateFromProps = component.constructor.getDerivedStateFromProps; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's usually smaller post-gzip to skip an intermediary variable like this one. Any chance that's the case here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, in this case it appears to not move the byte count at all. 3480 before and 3480 after the change (you can see it in commit b165370). So which do you prefer? Intermediary variable or not? Happy to update it to which ever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spoke too soon. When this change is combined with other changes, it could save an additional byte. I've updated the code to remove the intermediary variable.
f895912
to
7a905ae
Compare
7a905ae
to
401261a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🎉 @andrewiggins I'm really in awe of the thoroughness of this PR. You really went above all expectations by adding links to reacts unit tests and including them in our own suite.
Really solid and polished PR, thanks again for your contribution 🎉
} | ||
else if (component.componentWillReceiveProps) { | ||
component.componentWillReceiveProps(props, context); | ||
if (typeof component.constructor.getDerivedStateFromProps === 'undefined') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andrewiggins I think he meant removing the if
-statement completely.
@marvinhagemeister Thanks for kind words 😄. I really appreciate the hard work @developit, you, and the other Preact contributors have put into building a high quality library in so few bytes. I want/hope my PRs meet that same bar. |
Add support for the getDerivedStateFromProps lifecycle method.
Where componentWillMount or componentWillReceiveProps would've previously been called, Preact now invokes getDerivedStateFromProps on the component's constructor. The result of getDerivedStateFromProps is then immediately merged into the component's current state for use by the next lifecycle methods (primarily shouldComponentUpdate, render, and componentDidUpdate).
Sizes (bytes gzipped)
Before: 3443
After: 3477Change: +34commit 4150a25
After: 3480
Change: +37
Some differences from React
See comments for more discussion.
Updated sizes.