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 mergeCollection bug #66

Merged
merged 12 commits into from
Apr 26, 2021
Merged

Fix mergeCollection bug #66

merged 12 commits into from
Apr 26, 2021

Conversation

tgolen
Copy link
Collaborator

@tgolen tgolen commented Apr 22, 2021

Details

This fixes a bug found in mergeCollection

Related Issues

https://github.com/Expensify/Expensify/issues/161384

Automated Tests

There is a test added which properly tests the behavior

Linked PRs

Expensify/App#2543

@tgolen tgolen requested review from chiragsalian, marcaaron and a team April 22, 2021 22:01
@tgolen tgolen self-assigned this Apr 22, 2021
@github-actions
Copy link
Contributor

github-actions bot commented Apr 22, 2021

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@MelvinBot MelvinBot requested review from stitesExpensify and removed request for a team April 22, 2021 22:01
@tgolen
Copy link
Collaborator Author

tgolen commented Apr 22, 2021

I have read the CLA Document and I hereby sign the CLA

@tgolen tgolen requested a review from a team as a code owner April 22, 2021 22:15
@MelvinBot MelvinBot requested review from deetergp and removed request for a team April 22, 2021 22:15
Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

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

Looks great and nice catch!

lib/Onyx.js Outdated
// If `newData` happens to not exist, then set the state back to the previous value because
// if the state is set to an undefined value, then the item reverts to whatever is in
// defaultProps
[subscriber.statePropertyName]: typeof newData !== 'undefined' ? newData : prevState[subscriber.statePropertyName],
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use _.isUndefined() ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, yeah... that's a good call. I guess we do have underscore here.

@tgolen
Copy link
Collaborator Author

tgolen commented Apr 22, 2021

Updated to use underscore

lib/Onyx.js Outdated
// defaultProps
[subscriber.statePropertyName]: !_.isUndefined(newData)
? newData
: prevState[subscriber.statePropertyName],
Copy link
Contributor

Choose a reason for hiding this comment

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

Thinking more on this... this could maybe be filed under "unrelated" or "not a problem", but I find it interesting (or rather incorrect/inefficient) that if a subscriber is connected to a single key, but it's data hasn't changed at all ... that we are still calling setState() with previous data?

That's not super great because it means that we will still trigger re-renders on those connected components and for no reason at all. So, probably if we have undefined then we should do less than set the previous state again and should do... well... nothing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I think you make a good point. While it's not a problem currently, it's not a great design either. The reason I put it inside setState is because that's where it is getting newData. But now that I look at it closely, newData should be able to be retrieved before calling setState() and in the case where it is undefined, we can just skip calling setState(). How's that sound?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, sounds good to me. 👍

expect(onRender2.mock.calls[0][0].testObject).toStrictEqual({ID: 2});
expect(onRender2.mock.calls[1][0].testObject).toStrictEqual({ID: 2});
expect(onRender3.mock.calls[0][0].testObject).toStrictEqual({ID: 3});
expect(onRender3.mock.calls[1][0].testObject).toStrictEqual({ID: 3});
Copy link
Contributor

Choose a reason for hiding this comment

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

Like my expectation would be... not that this gets called again with same data but not called at all ? Why should it re-render since nothing has changed?

@tgolen
Copy link
Collaborator Author

tgolen commented Apr 22, 2021

OK, updated. Now with less re-renderings!

marcaaron
marcaaron previously approved these changes Apr 22, 2021
chiragsalian
chiragsalian previously approved these changes Apr 23, 2021
Copy link
Contributor

@chiragsalian chiragsalian left a comment

Choose a reason for hiding this comment

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

LGTM and i like the changes 👍

@tgolen
Copy link
Collaborator Author

tgolen commented Apr 23, 2021

OK, before merging this, I want to give this a little more testing because I haven't been able to test the most recent changes yet. Once I do that testing, I'll let you know when this has a green-light to merge.

@tgolen tgolen changed the title Fix mergeCollection bug [HOLD testing] Fix mergeCollection bug Apr 23, 2021
const onRender2 = jest.fn();
const onRender3 = jest.fn();

// GIVEN there is a collection with three simple items in it
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB: I REALLY appreciate the GIVEN/WHEN/THEN docs here! It makes it so much easier to follow what's going on and what we're testing for. :bigfan:

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I love that format for testing comments. @nkuoch was the one that originally showed it to me!

deetergp
deetergp previously approved these changes Apr 23, 2021
Copy link
Contributor

@deetergp deetergp left a comment

Choose a reason for hiding this comment

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

Looks good to me

@tgolen
Copy link
Collaborator Author

tgolen commented Apr 23, 2021

OK, I think I found another bug in my testing, but I want to write a test case to confirm that theory. Will continue this on Monday.

@tgolen tgolen changed the title [HOLD testing] Fix mergeCollection bug Fix mergeCollection bug Apr 26, 2021
@tgolen
Copy link
Collaborator Author

tgolen commented Apr 26, 2021

All right, I am taking off the hold here. When I tested this on Friday I found another bug with how the new collection data was being merged into the state. I have added a test case to cover that, fixed the bug (using spread operators instead of lodash.merge()), and I've tested this in the IOU PR to ensure it works the way it's supposed to.

Copy link
Contributor

@chiragsalian chiragsalian left a comment

Choose a reason for hiding this comment

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

LGTM

subscriber.withOnyxInstance.setState(prevState => ({
[subscriber.statePropertyName]: _.isObject(dataFromCollection)
? {
...prevState[subscriber.statePropertyName],
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems potentially dangerous as it assumes we will always have at least an empty object for prevState[subscriber.statePropertyName] if we are dealing with object shaped data. If we try to spread something like undefined or null then we get 💥.

Maybe some combination of lodash.merge() could still be helpful here to help guard against that?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe there's no problem with that actually. I could just have made that up in my head.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think spreading undefined in objects works OK but arrays doesn't.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the small difference between this and using lodashMerge() is that the spread is not going to recursively merge the data. So depending on how complicated that data is we could get some weird results. But we can address that if it becomes a problem.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Cool, thanks Marc. I think it'll be OK for now. Seeing as how everyone has approved this, I'm going to go ahead and merge it.

@tgolen tgolen merged commit accabbd into master Apr 26, 2021
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