-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Optimistic update for subscriptions #5267
Comments
Wondering the same, also using Hasura. Follow. |
For hasura, there seems to exist is a workaround, as suggested by @heyrict here: hasura/graphql-engine#2317 (comment) Basically you'll have a query instead of a subscription and you'll use the queries subscribeToMore capability to your subscription. The query can then be updated by optimisticResponse and will also be updated by the subscription. I still think that subscriptions should have some sort of cache API like queries do and they should be automatically updated by optimisticResponse. |
So I've been running into this as well (also using Hasura). In my case, I found that As pointed out by @bkniffler, as well as kinda mentioned in the ApolloClient docs, if you want to have a GQL subscription interact with the cache (and generally behave the way ApolloClient queries behave), you need to use the In an Angular app, I decided to simplify this by extending class CustomApolloClient extends ApolloClient {
liveQuery<T, V>(options: Omit<WatchQueryOptions<V>, 'pollInterval'>) {
const { query } = options;
const queryString = query.loc!.source.body;
const subscription = gql(queryString.replace('query', 'subscription'));
const watchQuery = this.watchQuery<T, V>(options);
let subscribeToMoreTeardown: (() => void) | undefined;
return watchQuery.valueChanges.pipe(
tapOnSubscribe(() => {
subscribeToMoreTeardown = watchQuery.subscribeToMore({
document: subscription,
updateQuery: (_, curr) => curr.subscriptionData.data,
variables: options.variables,
});
}),
finalize(() => {
subscribeToMoreTeardown!();
}),
share(),
);
}
}
/** Triggers callback every time a new observer subscribes to this chain. */
function tapOnSubscribe<T>(callback: () => void): MonoTypeOperatorFunction<T> {
return (source: Observable<T>): Observable<T> =>
defer(() => {
callback();
return source;
});
} This method receives a single Hope this helps. |
@thefliik hey this is really great. I'm curious how this has worked out for you so far in production, are you happy with this solution? I'm planning on doing something similar. |
Greetings Apollo team. Any plans to make subscriptions updatable from the cache after mutations? |
As mentioned above you kinda have to use both subscriptions and queries to get both live updates and optimistic responses. This is how I do it with React hooks alone. I have a functional React component that uses both I tried to simplify and clean up a code example below but realized const postQueryString = `
query FetchJobBoardPost{
jobBoardPosts: post_by_pk(id: "1234"){
id title
}
}
`;
const POST_QUERY = gql`${groupingQueryString}`;
const POST_SUBSCRIPTION = gql`${gpostQueryString.replace('query', 'subscription')}`;
const JobBoard = (props) => {
const [updatePostMutate] = useMutation(UPDATE_POST_MUTATION);
const postQueryResult = useQuery(POST_QUERY);
const postSubscriptionResult = useSubscription(POST_SUBSCRIPTION);
const handleButtonClick = () => {
const optimisticResponse = {
__typename: 'Mutation',
updatePost: {
id: '123',
__typename: 'post',
title: newTitle,
},
};
updatePostMutate({
variables: { newTitle },
optimisticResponse,
});
}
// React UI here using postQueryResult
} |
@nolandg |
@jdgamble555 Not sure what you mean. |
@nolandg - I apologize for my ignorance, as I am an angular, typescript, and svelte developer, not react... So I am assuming the fact that it is not reference indicates the same thing as if Since it is not referenced, I imagine you could just run |
@jdgamble555 Yes, you could just call I suppose not assigning it though would make the fact that there are intended side effects more obvious. |
Because I didn't know to expand my Apollo client like what @thefliik did and because I use React, I instead created a custom hook to create the same behavior as his example (having the subscription data in the cache to be able to modify the cache and create optimistic update). Note that my logic is made to be used with Hasura but it's very easy to change it for any subscription technologies. The hook export const useHasuraSubscriptionWithCache = (
queryDocument: Apollo.DocumentNode,
options?: any
) => {
const queryString = queryDocument.loc.source.body;
const subscriptionDocument = gql(
queryString.replace('query', 'subscription')
);
const queryDocumentResult = Apollo.useQuery(queryDocument, {
variables: options?.variables,
});
useEffect(() => {
if (queryDocumentResult?.subscribeToMore) {
const unsubscribe = queryDocumentResult.subscribeToMore({
document: subscriptionDocument,
updateQuery: (_, curr) => {
return curr.subscriptionData.data;
},
variables: options?.variables,
});
return () => unsubscribe();
}
}, [options?.variables, queryDocumentResult, subscriptionDocument]);
return queryDocumentResult;
}; How to use it const scheduleProjectsData = useHasuraSubscriptionWithCache(
ScheduleProjectsDataDocument
); How to change the cache in the mutation to create an optimistic update projectDateUpdate({
variables: {
input: {
id: id,
start: startDate,
end: endDate,
},
},
update: (cache) => {
const data = cache.readQuery({
query: ScheduleProjectsDataDocument,
});
if (!data) {
return;
}
cache.writeQuery({
query: ScheduleProjectsDataDocument,
data: {
...data,
projects: calculateOptimisticProjectMoveData(
data.projects,
id,
startDate,
endDate
),
},
});
},
}); |
Is there any plan to fix this issue ? When we use a subscription, each value received will update the apollo client cache, and therefore trigger a rerender on every It is, in my opinion, misleading that the cache update only work in one way. It should always work, or never, but not something between. Is there any plan to fix this? Would a PR on this subject be appreciated? I don't know if this kind of behavior changes is ok for a minor version ? |
@EmrysMyrddin FWIW, I would also love to see this changed but I definitely think it would be breaking and require a major version bump. I think the current implementation is intentional and hence, even if we disagree with it, not a "bug". When subscriptions were first rolled out, the impression I got is that their intended use was as small, real-time "supplements" to "normal" http requests. The idea was that apps like github, which are largely not realtime, could add small realtime features (like, in real time, adding the dot next to the notifications icon when a new notification comes in). These subscriptions were subscribing to events, rather than data (e.g. the "notification added" event). Today though, subscriptions are often used as a mechanism for subscribing to real-time data rather than to an event (live queries were suppose to be our method for subscribing to real-time data, but the GraphQL spec moves slow and people are impatient and we all just co-opted subscriptions. Last I checked [admittedly a while ago], the GraphQL spec has now largely said, "well if subscriptions work I guess we don't need to do anything else after all."--if you're unfamiliar, the problem with subscriptions is that they return everything each time they fire, rather than just the diff like a "live query" was suppose to do). If you're subscribing to events, it makes sense that you only return events as they happen, and nothing from the cache. If you subscribe to data though, then this doesn't make sense (you'll generally want an optimistic update immediately). So I think the current Apollo strategy made sense at the time, but is now just outdated. Hence, it's appropriate to change the implementation but it definitely is a breaking change. Alternatively, providing some way to "opt-in" to optimistic subscription results would be a way to slide this functionality in as a minor release. This is probably the best solution. |
I had a similar argument with URQL. It seems they are not going to fix this since the GraphQL spec does not say it is necessary. The GraphQL spec needs to change first apparently, although I don't understand what the spec specifically says about this: urql-graphql/urql#1423 |
Hey all 👋 Thanks for the discussion here! I've just read through the linked urql discussion and, without restating them all here, I agree with the points raised by its maintainers about why optimistic updates don't make sense within the programming model of subscriptions. That being said, our team at Apollo has been thinking about the future of real-time data in GraphQL (in fact, that was the title of @benjamn's keynote delivered at GraphQL Summit this past October 😄 video, slides). To stay up to date (pun intended... 🥁), consider joining our Discord as we share more in the coming months. For now, I'm going to close this issue out, but thank you for being a part of the Apollo community and hope to see you online! |
I've got a subscription (a hasura server live-query) like:
When doing a mutation with optimisticResponse set, the subscription will not be updated optimistically. If I use a the same gql, but instead as a query, everything works perfectly as expected. I guess that subscription cache is currently not updated by optimisticResponse. Also, manual cache updating will not work since subscription results can't be retrieved by the cache (no cache.readSubscription).
Is there a particular reason why subscriptions don't have any optimistic capabilities? Is this a bug or more of a feature request? Sorry for not filling the whole template. I can go and create a reproducible example, but I'm not yet sure if absence of optimism in subscriptions is "by design".
The text was updated successfully, but these errors were encountered: