diff --git a/packages/docs/src/api/use-mutation.md b/packages/docs/src/api/use-mutation.md index c65aa643..81dde5a1 100644 --- a/packages/docs/src/api/use-mutation.md +++ b/packages/docs/src/api/use-mutation.md @@ -47,6 +47,6 @@ - `called`: boolean `Ref` holding `true` if the mutation was already called. -- `onDone`: Event hook called when the mutation successfully completes. +- `onDone(handler)`: Event hook called when the mutation successfully completes. Handler is called with: `result` (mutation result) and `context` which is an object with `client` (ApolloClient instance). -- `onError`: Event hook called when an error occurs. +- `onError(handler)`: Event hook called when an error occurs. Handler is called with: `error` and `context` which is an object with `client` (ApolloClient instance). diff --git a/packages/docs/src/api/use-query.md b/packages/docs/src/api/use-query.md index b088a6a3..78b42fa4 100644 --- a/packages/docs/src/api/use-query.md +++ b/packages/docs/src/api/use-query.md @@ -60,7 +60,7 @@ - `subscribeToMore(options)`: Add a subscription to the query, useful to add new data received from the server in real-time. See [Subscription](../guide-composable/subscription#subscribetomore). -- `onResult(handler)`: Event hook called when a new result is available. +- `onResult(handler)`: Event hook called when a new result is available. Handler is called with: `result` (query result) and `context` which is an object with `client` (ApolloClient instance). -- `onError(handler)`: Event hook called when an error occurs. +- `onError(handler)`: Event hook called when an error occurs. Handler is called with: `error` and `context` which is an object with `client` (ApolloClient instance). diff --git a/packages/docs/src/api/use-subscription.md b/packages/docs/src/api/use-subscription.md index 341fbb23..9146829f 100644 --- a/packages/docs/src/api/use-subscription.md +++ b/packages/docs/src/api/use-subscription.md @@ -33,7 +33,7 @@ - `variables`: Ref holding the variables object. -- `onResult(handler)`: Event hook called when a new result is available. +- `onResult(handler)`: Event hook called when a new result is available. Handler is called with: `result` (new result) and `context` which is an object with `client` (ApolloClient instance). -- `onError(handler)`: Event hook called when an error occurs. +- `onError(handler)`: Event hook called when an error occurs. Handler is called with: `error` and `context` which is an object with `client` (ApolloClient instance). diff --git a/packages/docs/src/guide-composable/subscription.md b/packages/docs/src/guide-composable/subscription.md index 917c0734..b05cde85 100644 --- a/packages/docs/src/guide-composable/subscription.md +++ b/packages/docs/src/guide-composable/subscription.md @@ -403,7 +403,7 @@ This is called when a new result is received from the server: ```js const { onResult } = useSubscription(...) -onResult(result => { +onResult((result, context) => { console.log(result.data) }) ``` @@ -417,11 +417,48 @@ import { logErrorMessages } from '@vue/apollo-util' const { onError } = useSubscription(...) -onError(error => { +onError((error, context) => { logErrorMessages(error) }) ``` +### Update the cache + +Using `onResult`, you can update the Apollo cache with the new data: + +```js +const { onResult } = useSubscription(...) + +onResult((result, { client }) => { + const query = { + query: gql`query getMessages ($channelId: ID!) { + messages(channelId: $channelId) { + id + text + } + }`, + variables: { + channelId: '123', + }, + } + + // Read the query + let data = client.readQuery(query) + + // Update cached data + data = { + ...data, + messages: [...data.messages, result.data.messageAdded], + } + + // Write back the new result for the query + client.writeQuery({ + ...query, + data, + }) +}) +``` + ## subscribeToMore With GraphQL subscriptions your client will be alerted on push from the server and you should choose the pattern that fits your application the most: diff --git a/packages/vue-apollo-composable/src/useMutation.ts b/packages/vue-apollo-composable/src/useMutation.ts index 4f7aa49f..6c95c72a 100644 --- a/packages/vue-apollo-composable/src/useMutation.ts +++ b/packages/vue-apollo-composable/src/useMutation.ts @@ -1,5 +1,5 @@ import { DocumentNode } from 'graphql' -import { MutationOptions, OperationVariables, FetchResult, TypedDocumentNode, ApolloError } from '@apollo/client/core/index.js' +import { MutationOptions, OperationVariables, FetchResult, TypedDocumentNode, ApolloError, ApolloClient } from '@apollo/client/core/index.js' import { ref, onBeforeUnmount, isRef, Ref, getCurrentInstance } from 'vue-demi' import { useApolloClient } from './useApolloClient' import { ReactiveFunction } from './util/ReactiveFunction' @@ -25,15 +25,23 @@ export type MutateOverrideOptions = Pick = Promise, Record> | null> export type MutateFunction = (variables?: TVariables | null, overrideOptions?: MutateOverrideOptions) => MutateResult +export interface OnDoneContext { + client: ApolloClient +} + +export interface OnErrorContext { + client: ApolloClient +} + export interface UseMutationReturn { mutate: MutateFunction loading: Ref error: Ref called: Ref - onDone: (fn: (param: FetchResult, Record>) => void) => { + onDone: (fn: (param: FetchResult, Record>, context: OnDoneContext) => void) => { off: () => void } - onError: (fn: (param: ApolloError) => void) => { + onError: (fn: (param: ApolloError, context: OnErrorContext) => void) => { off: () => void } } @@ -51,8 +59,8 @@ export function useMutation< const error = ref(null) const called = ref(false) - const doneEvent = useEventHook, Record>>() - const errorEvent = useEventHook() + const doneEvent = useEventHook<[FetchResult, Record>, OnDoneContext]>() + const errorEvent = useEventHook<[ApolloError, OnErrorContext]>() // Apollo Client const { resolveClient } = useApolloClient() @@ -92,13 +100,17 @@ export function useMutation< : undefined, }) loading.value = false - doneEvent.trigger(result) + doneEvent.trigger(result, { + client, + }) return result } catch (e) { const apolloError = toApolloError(e) error.value = apolloError loading.value = false - errorEvent.trigger(apolloError) + errorEvent.trigger(apolloError, { + client, + }) if (currentOptions.throws === 'always' || (currentOptions.throws !== 'never' && !errorEvent.getCount())) { throw apolloError } diff --git a/packages/vue-apollo-composable/src/useQuery.ts b/packages/vue-apollo-composable/src/useQuery.ts index 5b051c63..87d05f4e 100644 --- a/packages/vue-apollo-composable/src/useQuery.ts +++ b/packages/vue-apollo-composable/src/useQuery.ts @@ -22,6 +22,7 @@ import type { ObservableSubscription, TypedDocumentNode, ApolloError, + ApolloClient, } from '@apollo/client/core/index.js' import { throttle, debounce } from 'throttle-debounce' import { useApolloClient } from './useApolloClient' @@ -58,6 +59,14 @@ export type DocumentParameter = DocumentNode | Ref = TVariables | Ref | ReactiveFunction export type OptionsParameter = UseQueryOptions | Ref> | ReactiveFunction> +export interface OnResultContext { + client: ApolloClient +} + +export interface OnErrorContext { + client: ApolloClient +} + // Return export interface UseQueryReturn { result: Ref @@ -75,10 +84,10 @@ export interface UseQueryReturn refetch: (variables?: TVariables) => Promise> | undefined fetchMore: (options: FetchMoreQueryOptions & FetchMoreOptions) => Promise> | undefined subscribeToMore: (options: SubscribeToMoreOptions | Ref> | ReactiveFunction>) => void - onResult: (fn: (param: ApolloQueryResult) => void) => { + onResult: (fn: (param: ApolloQueryResult, context: OnResultContext) => void) => { off: () => void } - onError: (fn: (param: ApolloError) => void) => { + onError: (fn: (param: ApolloError, context: OnErrorContext) => void) => { off: () => void } } @@ -157,9 +166,9 @@ export function useQueryImpl< * Result from the query */ const result = ref() - const resultEvent = useEventHook>() + const resultEvent = useEventHook<[ApolloQueryResult, OnResultContext]>() const error = ref(null) - const errorEvent = useEventHook() + const errorEvent = useEventHook<[ApolloError, OnErrorContext]>() // Loading @@ -217,6 +226,10 @@ export function useQueryImpl< // Apollo Client const { resolveClient } = useApolloClient() + function getClient () { + return resolveClient(currentOptions.value?.clientId) + } + // Query const query: Ref | null | undefined> = shallowRef() @@ -242,7 +255,7 @@ export function useQueryImpl< error.value = null loading.value = true - const client = resolveClient(currentOptions.value?.clientId) + const client = getClient() query.value = client.watchQuery({ query: currentDocument, @@ -328,7 +341,9 @@ export function useQueryImpl< networkStatus.value = queryResult.networkStatus // Wait for handlers to be registered nextTick(() => { - resultEvent.trigger(queryResult) + resultEvent.trigger(queryResult, { + client: getClient(), + }) }) } @@ -357,7 +372,9 @@ export function useQueryImpl< networkStatus.value = 8 // Wait for handlers to be registered nextTick(() => { - errorEvent.trigger(apolloError) + errorEvent.trigger(apolloError, { + client: getClient(), + }) }) } diff --git a/packages/vue-apollo-composable/src/useSubscription.ts b/packages/vue-apollo-composable/src/useSubscription.ts index b404c306..1a152dd4 100644 --- a/packages/vue-apollo-composable/src/useSubscription.ts +++ b/packages/vue-apollo-composable/src/useSubscription.ts @@ -17,6 +17,7 @@ import type { ObservableSubscription, TypedDocumentNode, ApolloError, + ApolloClient, } from '@apollo/client/core/index.js' import { throttle, debounce } from 'throttle-debounce' import { ReactiveFunction } from './util/ReactiveFunction' @@ -44,6 +45,14 @@ type DocumentParameter = DocumentNode | Ref | type VariablesParameter = TVariables | Ref | ReactiveFunction type OptionsParameter = UseSubscriptionOptions | Ref> | ReactiveFunction> +export interface OnResultContext { + client: ApolloClient +} + +export interface OnErrorContext { + client: ApolloClient +} + export interface UseSubscriptionReturn { result: Ref loading: Ref @@ -55,10 +64,10 @@ export interface UseSubscriptionReturn { variables: Ref options: UseSubscriptionOptions | Ref> subscription: Ref, Record>> | null> - onResult: (fn: (param: FetchResult, Record>) => void) => { + onResult: (fn: (param: FetchResult, Record>, context: OnResultContext) => void) => { off: () => void } - onError: (fn: (param: ApolloError) => void) => { + onError: (fn: (param: ApolloError, context: OnErrorContext) => void) => { off: () => void } } @@ -119,9 +128,9 @@ export function useSubscription < const optionsRef = paramToReactive(options) const result = ref() - const resultEvent = useEventHook>() + const resultEvent = useEventHook<[FetchResult, OnResultContext]>() const error = ref(null) - const errorEvent = useEventHook() + const errorEvent = useEventHook<[ApolloError, OnErrorContext]>() const loading = ref(false) vm && trackSubscription(loading) @@ -133,12 +142,16 @@ export function useSubscription < let observer: ObservableSubscription | null = null let started = false + function getClient () { + return resolveClient(currentOptions.value?.clientId) + } + function start () { if (started || !isEnabled.value || isServer) return started = true loading.value = true - const client = resolveClient(currentOptions.value?.clientId) + const client = getClient() subscription.value = client.subscribe({ query: currentDocument, @@ -155,7 +168,9 @@ export function useSubscription < function onNextResult (fetchResult: FetchResult) { result.value = fetchResult.data loading.value = false - resultEvent.trigger(fetchResult) + resultEvent.trigger(fetchResult, { + client: getClient(), + }) } function onError (fetchError: unknown) { @@ -163,7 +178,9 @@ export function useSubscription < error.value = apolloError loading.value = false - errorEvent.trigger(apolloError) + errorEvent.trigger(apolloError, { + client: getClient(), + }) } function stop () { diff --git a/packages/vue-apollo-composable/src/util/useEventHook.ts b/packages/vue-apollo-composable/src/util/useEventHook.ts index 43bd7d19..abffb19a 100644 --- a/packages/vue-apollo-composable/src/util/useEventHook.ts +++ b/packages/vue-apollo-composable/src/util/useEventHook.ts @@ -1,23 +1,23 @@ -export function useEventHook () { - const fns: Array<(param: TParam) => void> = [] +export function useEventHook () { + const fns: Array<(...params: TParams) => void> = [] - function on (fn: (param: TParam) => void) { + function on (fn: (...params: TParams) => void) { fns.push(fn) return { off: () => off(fn), } } - function off (fn: (param: TParam) => void) { + function off (fn: (...params: TParams) => void) { const index = fns.indexOf(fn) if (index !== -1) { fns.splice(index, 1) } } - function trigger (param: TParam) { + function trigger (...params: TParams) { for (const fn of fns) { - fn(param) + fn(...params) } }