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

ObservableQuery is not updated by writeQuery when unsubscribed #12268

Open
diesieben07 opened this issue Jan 14, 2025 · 2 comments
Open

ObservableQuery is not updated by writeQuery when unsubscribed #12268

diesieben07 opened this issue Jan 14, 2025 · 2 comments

Comments

@diesieben07
Copy link

Issue Description

An ObservableQuery retains its old (outdated) value if the cache is written to while no subscriptions are active.

Link to Reproduction

https://codesandbox.io/p/devbox/xzzldg

Reproduction Steps

  1. Use client.watchQuery and subscribe to the Observable.
  2. When the value is loaded, unsubscribe from the Observable.
  3. Use client.writeQuery to overwrite the query data.
  4. Subscribe to the Observable again observe that it does not emit the overwritten data, but instead the old data, which is outdated and not what is present in the cache.

@apollo/client version

3.12.15

@phryneas
Copy link
Member

Hi @diesieben07!

If you adjust the example slightly, you will see that while the Observable will immediately return with the last value it observed while it was active, it will also respond with a more up-to-date value from cache immediately after.

const queryRef = client.watchQuery({ query });
let promise, resolve;
promise = new Promise((r) => (resolve = r));

const s = queryRef.subscribe((v) => {
  console.log("subscription", v);
  s.unsubscribe();
  resolve();
});

await promise;

client.writeQuery({
  query,
  data: {
    company: {
      __typename: "Info",
      ceo: "Me!",
    },
  },
});
const s2 = queryRef.subscribe((v) => {
  console.log("subscription2", v);
});

setTimeout(() => {
  s2.unsubscribe();
}, 500);

So here the argument can kinda be made into both directions - is it valid not to immediately have a value (if your fetchPolicy were no-cache, you would have to wait for a network request) or to immediately have something, followed up by a corrected value?

Can you explain a bit what problems you are running into with the current behaviour?

@diesieben07
Copy link
Author

diesieben07 commented Jan 16, 2025

Thank you for your response!

Can you explain a bit what problems you are running into with the current behaviour?

The problem manifested for me as follows, when using Apollo in Angular (but could be reproduced in any other FE framework using the same techniques):

  • UserService has a QueryRef for the loggedInUser query.
  • The / route has a guard to make sure it can only be visited by logged in users. This guard waits for the first value from loggedInUser query (via the QueryRef in the service)

The following now happens when a logged-in user visits the page and then logs out:

  1. / route guard sees the logged in user from the query and passes: User sees the home page.
  2. User clicks "Logout" and navigates to /logout. The code runs the logout mutation and gets "Anonymous User" back (logout successful). In the mutation update is used to write the loggedInUser query to update the local cache. When the mutation is complete, the user navigates back to / via the "Back to home" link.
  3. The / route guard is again executed and gets the first result from the loggedInUser QueryRef. This is still the logged in user (the stale result). The guard passes and the user goes to the root page even though they are not logged in.

The way I fixed it is to not share the QueryRef, but instead always call watchQuery anew when necessary.

I don't know anything about the React side of Apollo, but to me it seems like this problem has been recognized by the React hooks, because of this call to reinitialize. reinitialize gets rid of the last result that the query has stored, so the query will immediately go to the cache instead of first producing a stale result.

The problem can also be fixed by calling QueryRef#resetLastResults (which is called by reinitialize) before subscribing again, but I would expect QueryRef to do this on its own.

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

No branches or pull requests

3 participants