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

Various improvements around previousResult/newData change detection. #4032

Merged
merged 6 commits into from
Oct 22, 2018

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Oct 20, 2018

Fixes #3992, along with a few other miscellaneous improvements.

As #3992 (and specifically the reproduction that @OurMajesty provided) demonstrates, now that the InMemoryCache may return the same object for multiple reads (when the data have not changed), the previousResult is often exactly (===) the same object as newData.result, which was causing the code removed by this commit to return early instead of broadcasting the watch.

This early return was unsafe, because the contents of the object may have changed since they were previously broadcast, so we actually do need to report those modifications, even though the object reference is the same.

In other words, previousResult === newData.result does not imply "nothing has changed," as I mistakenly assumed in this discussion: #3394 (comment)

In the longer term, I would like to eliminate the previousResult logic entirely, since it seems redundant now that we can precisely track changes to the store, but for now it's important for backwards compatibility. Specifically, previousResult still provides a way to prefer the previous object if it is structurally identical to the new object, though of course that preference is moot when previousResult === newData.result.

The previousResult object should never be used as a basis for skipping broadcasts. Only the application developer can decide whether === object identity means it's safe to skip rendering, likely in the context of a larger design which treats cache results as immutable data. The job of the
InMemoryCache is to make no unsound assumptions, and to broadcast results whenever they may have changed.

May help with apollographql/react-apollo#2126,
since this code was only making a shallow copy of defaultQueryInfo, so
there's a risk that defaultQueryInfo.listeners and .subscriptions could
have grown over time, without ever getting garbage collected.
Should help fix #3992.

As #3992 (and specifically the reproduction that @OurMajesty provided at
https://codesandbox.io/s/q7rkm6y1ow) demonstrates, since the InMemoryCache
may return the same object for multiple reads (when the data have not
changed), the previousResult is often exactly (===) the same object as
newData.result, which was causing the code removed by this commit to
return early instead of broadcasting the watch.

This early return was unsafe, because the contents of the object may have
changed since they were previously broadcast, so we actually do need to
report those modifications, even though the object reference is the same.

In other words, `previousResult === newData.result` does not imply
"nothing has changed," as I mistakenly assumed in this discussion:
#3394 (comment)

In the longer term, I would like to eliminate the previousResult logic
entirely, since it seems redundant now that we can precisely track changes
to the store, but for now it's important for backwards compatibility.
Specifically, previousResult still provides a way to prefer the previous
object if it is structurally identical to the new object, though that
preference is moot when `previousResult === newData.result`.

The previousResult object should never be used as a basis for skipping
broadcasts. Only the application developer can decide whether === object
identity means it's safe to skip rendering, likely in the context of a
larger design which treats cache results as immutable data. The job of the
InMemoryCache is to make no unsound assumptions, and broadcast results
whenever they may have changed.
If you go to the trouble of detecting cycles, you should clone them
accurately rather than ignoring them!
Should help with #3992.

Though deep cloning and isEqual testing are obviously more expensive than
relying on === equality, the reality is that lastResult may have been
modified since it was previously recorded, so === equality is useless for
determining isDifferentResult. A defensive copy is the only way to make
sure we get this right.
@benjamn benjamn merged commit 48a224d into master Oct 22, 2018
@benjamn benjamn deleted the fix-issue-3992 branch October 22, 2018 23:05
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant