Skip to content

Add getSnapshotBeforeUpdate lifecycle hook#1112

Merged
marvinhagemeister merged 10 commits into
preactjs:masterfrom
marvinhagemeister:snapshot_lifecycle
May 27, 2018
Merged

Add getSnapshotBeforeUpdate lifecycle hook#1112
marvinhagemeister merged 10 commits into
preactjs:masterfrom
marvinhagemeister:snapshot_lifecycle

Conversation

@marvinhagemeister

@marvinhagemeister marvinhagemeister commented May 20, 2018

Copy link
Copy Markdown
Member
  • Add getSnapshotBeforeUpdate tests
  • Port unit tests from react
  • Add unit tests

@coveralls

coveralls commented May 20, 2018

Copy link
Copy Markdown

Coverage Status

Coverage remained the same at 100.0% when pulling 02abb3f on marvinhagemeister:snapshot_lifecycle into 1b898e2 on developit:master.

Comment thread test/browser/lifecycle.js Outdated
// [should not override state with stale values if prevState is spread within getDerivedStateFromProps](https://github.com/facebook/react/blob/25dda90c1ecb0c662ab06e2c80c1ee31e0ae9d36/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js#L1035)
});

describe.only("#getSnapshotBeforeUpdate", () => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

TODO: remove only

Comment thread src/vdom/component.js Outdated
rendered, inst, cbase;

// FIXME: Do we have an issue with previous state??
const prev = extend({}, previousState);

@marvinhagemeister marvinhagemeister May 21, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Dirty workaround: I have the suspicion that previousState is mutated somewhere. Caught this with the tests for getSnapshotBeforeUpdate by react.

TODO: Check what's happening here. Seems fishy and even more worrying is that we don't seem to have a test for that yet :S

@marvinhagemeister marvinhagemeister changed the title WIP: Add getSnapshotBeforeUpdate lifecycle hook Add getSnapshotBeforeUpdate lifecycle hook May 22, 2018
Comment thread src/vdom/component.js Outdated
context = component.context,
previousProps = component.prevProps || props,
previousState = component.prevState || state,
previousState = extend({}, component.prevState || state),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Although this is needed for getSnapshotBeforeUpdate this also fixes a bug with componentDidUpdate. During rendering, the state variable is mutated. This has the consequence, that previousState would never hold the previous state, but the most current one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

where were we mutating previousState?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, should have debugged this further. The mutation happens when getDerivedStateFromProps is used. Moved the extend call there, so that we limit the cloning and only do it when necessary.

@marvinhagemeister

Copy link
Copy Markdown
Member Author

This PR is now ready to review 🎉

Comment thread src/vdom/component.js
initialBase = isUpdate || nextBase,
initialChildComponent = component._component,
skip = false,
snapshot = previousContext,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Q: were we deviating from React here by passing context as a 3rd argument to componentDidUpdate? If so, maybe we correct that here.

@marvinhagemeister marvinhagemeister May 23, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yep, we were/are deviating from React there. React never had a third context parameter. Same for shouldComponentUpdate. I'd prefer to make these changes in a separate PR, because they are independent from implementing getSnapshotBeforeUpdate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed regarding the separate PR, but we should make sure they go out in the same release - we'll save a few bytes by dropping that reassignment.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

mh I have a branch where I removed the non-standard context arguments, but I get the exact same gzip size as in master :S

@andrewiggins andrewiggins left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

Comment thread test/browser/lifecycle.js Outdated
});

describe("#getSnapshotBeforeUpdate", () => {
it('should call nested new lifecycle methods in the right order', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Love this test! This might be out of scope for this PR, but could we move this test to be directly under the "Lifecycle methods" describe block or put it in its own describe block? This test specificies the behavior of more than just getSnapshotBeforeUpdate. It specifies the order of (potentially all) lifecycle methods. I think it would be valuable to expand this test to include the order of the lifecycle methods for all 4 main render "events": mounting, props update, state update, and unmounting.

I started a test similar to this when implementing getDerivedStateFromProps but pulled it out as it was getting messy. This implementation is much more succinct. I'd be happy to expand this test in another PR if you think its useful.

@marvinhagemeister marvinhagemeister May 27, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That's a great suggestion! Moved the test to the top describe-block 🎉

I'm a bit hesitant to add componentWillUpdate, componentWillMount and componentWillReceiveProps to the test, because we'll follow react and mark them as deprecated with our next release.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yea, that makes total sense. No need to add them

Comment thread src/vdom/component.js

if (component.constructor.getDerivedStateFromProps) {
state = component.state = extend(state, component.constructor.getDerivedStateFromProps(props, state));
previousState = extend({}, previousState);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good catch - thanks!

Comment thread test/browser/lifecycle.js
]);
});

it('should pass the return value from getSnapshotBeforeUpdate to componentDidUpdate', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I appreciate the mirroring of the React UT suite 😄

@reznord reznord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@marvinhagemeister marvinhagemeister merged commit 78ce6cd into preactjs:master May 27, 2018
@marvinhagemeister marvinhagemeister deleted the snapshot_lifecycle branch May 27, 2018 19:29
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.

5 participants