Skip to content

Commit

Permalink
feat: new context params in event hook handlers
Browse files Browse the repository at this point in the history
Allow easy access to the client, especially useful with useSubscription().
  • Loading branch information
Akryum committed Nov 29, 2023
1 parent c9b648d commit 0be5d9b
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 35 deletions.
4 changes: 2 additions & 2 deletions packages/docs/src/api/use-mutation.md
Expand Up @@ -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).
4 changes: 2 additions & 2 deletions packages/docs/src/api/use-query.md
Expand Up @@ -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).

4 changes: 2 additions & 2 deletions packages/docs/src/api/use-subscription.md
Expand Up @@ -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).

41 changes: 39 additions & 2 deletions packages/docs/src/guide-composable/subscription.md
Expand Up @@ -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)
})
```
Expand All @@ -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:
Expand Down
26 changes: 19 additions & 7 deletions 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'
Expand All @@ -25,15 +25,23 @@ export type MutateOverrideOptions<TResult> = Pick<UseMutationOptions<TResult, Op
export type MutateResult<TResult> = Promise<FetchResult<TResult, Record<string, any>, Record<string, any>> | null>
export type MutateFunction<TResult, TVariables> = (variables?: TVariables | null, overrideOptions?: MutateOverrideOptions<TResult>) => MutateResult<TResult>

export interface OnDoneContext {
client: ApolloClient<any>
}

export interface OnErrorContext {
client: ApolloClient<any>
}

export interface UseMutationReturn<TResult, TVariables> {
mutate: MutateFunction<TResult, TVariables>
loading: Ref<boolean>
error: Ref<ApolloError | null>
called: Ref<boolean>
onDone: (fn: (param: FetchResult<TResult, Record<string, any>, Record<string, any>>) => void) => {
onDone: (fn: (param: FetchResult<TResult, Record<string, any>, Record<string, any>>, context: OnDoneContext) => void) => {
off: () => void
}
onError: (fn: (param: ApolloError) => void) => {
onError: (fn: (param: ApolloError, context: OnErrorContext) => void) => {
off: () => void
}
}
Expand All @@ -51,8 +59,8 @@ export function useMutation<
const error = ref<ApolloError | null>(null)
const called = ref<boolean>(false)

const doneEvent = useEventHook<FetchResult<TResult, Record<string, any>, Record<string, any>>>()
const errorEvent = useEventHook<ApolloError>()
const doneEvent = useEventHook<[FetchResult<TResult, Record<string, any>, Record<string, any>>, OnDoneContext]>()
const errorEvent = useEventHook<[ApolloError, OnErrorContext]>()

// Apollo Client
const { resolveClient } = useApolloClient()
Expand Down Expand Up @@ -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
}
Expand Down
31 changes: 24 additions & 7 deletions packages/vue-apollo-composable/src/useQuery.ts
Expand Up @@ -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'
Expand Down Expand Up @@ -58,6 +59,14 @@ export type DocumentParameter<TResult, TVariables> = DocumentNode | Ref<Document
export type VariablesParameter<TVariables> = TVariables | Ref<TVariables> | ReactiveFunction<TVariables>
export type OptionsParameter<TResult, TVariables extends OperationVariables> = UseQueryOptions<TResult, TVariables> | Ref<UseQueryOptions<TResult, TVariables>> | ReactiveFunction<UseQueryOptions<TResult, TVariables>>

export interface OnResultContext {
client: ApolloClient<any>
}

export interface OnErrorContext {
client: ApolloClient<any>
}

// Return
export interface UseQueryReturn<TResult, TVariables extends OperationVariables> {
result: Ref<TResult | undefined>
Expand All @@ -75,10 +84,10 @@ export interface UseQueryReturn<TResult, TVariables extends OperationVariables>
refetch: (variables?: TVariables) => Promise<ApolloQueryResult<TResult>> | undefined
fetchMore: (options: FetchMoreQueryOptions<TVariables, TResult> & FetchMoreOptions<TResult, TVariables>) => Promise<ApolloQueryResult<TResult>> | undefined
subscribeToMore: <TSubscriptionVariables = OperationVariables, TSubscriptionData = TResult>(options: SubscribeToMoreOptions<TResult, TSubscriptionVariables, TSubscriptionData> | Ref<SubscribeToMoreOptions<TResult, TSubscriptionVariables, TSubscriptionData>> | ReactiveFunction<SubscribeToMoreOptions<TResult, TSubscriptionVariables, TSubscriptionData>>) => void
onResult: (fn: (param: ApolloQueryResult<TResult>) => void) => {
onResult: (fn: (param: ApolloQueryResult<TResult>, context: OnResultContext) => void) => {
off: () => void
}
onError: (fn: (param: ApolloError) => void) => {
onError: (fn: (param: ApolloError, context: OnErrorContext) => void) => {
off: () => void
}
}
Expand Down Expand Up @@ -157,9 +166,9 @@ export function useQueryImpl<
* Result from the query
*/
const result = ref<TResult | undefined>()
const resultEvent = useEventHook<ApolloQueryResult<TResult>>()
const resultEvent = useEventHook<[ApolloQueryResult<TResult>, OnResultContext]>()
const error = ref<ApolloError | null>(null)
const errorEvent = useEventHook<ApolloError>()
const errorEvent = useEventHook<[ApolloError, OnErrorContext]>()

// Loading

Expand Down Expand Up @@ -217,6 +226,10 @@ export function useQueryImpl<
// Apollo Client
const { resolveClient } = useApolloClient()

function getClient () {
return resolveClient(currentOptions.value?.clientId)
}

// Query

const query: Ref<ObservableQuery<TResult, TVariables> | null | undefined> = shallowRef()
Expand All @@ -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<TResult, TVariables>({
query: currentDocument,
Expand Down Expand Up @@ -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(),
})
})
}

Expand Down Expand Up @@ -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(),
})
})
}

Expand Down
31 changes: 24 additions & 7 deletions packages/vue-apollo-composable/src/useSubscription.ts
Expand Up @@ -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'
Expand Down Expand Up @@ -44,6 +45,14 @@ type DocumentParameter<TResult, TVariables> = DocumentNode | Ref<DocumentNode> |
type VariablesParameter<TVariables> = TVariables | Ref<TVariables> | ReactiveFunction<TVariables>
type OptionsParameter<TResult, TVariables> = UseSubscriptionOptions<TResult, TVariables> | Ref<UseSubscriptionOptions<TResult, TVariables>> | ReactiveFunction<UseSubscriptionOptions<TResult, TVariables>>

export interface OnResultContext {
client: ApolloClient<any>
}

export interface OnErrorContext {
client: ApolloClient<any>
}

export interface UseSubscriptionReturn<TResult, TVariables> {
result: Ref<TResult | null | undefined>
loading: Ref<boolean>
Expand All @@ -55,10 +64,10 @@ export interface UseSubscriptionReturn<TResult, TVariables> {
variables: Ref<TVariables | undefined>
options: UseSubscriptionOptions<TResult, TVariables> | Ref<UseSubscriptionOptions<TResult, TVariables>>
subscription: Ref<Observable<FetchResult<TResult, Record<string, any>, Record<string, any>>> | null>
onResult: (fn: (param: FetchResult<TResult, Record<string, any>, Record<string, any>>) => void) => {
onResult: (fn: (param: FetchResult<TResult, Record<string, any>, Record<string, any>>, context: OnResultContext) => void) => {
off: () => void
}
onError: (fn: (param: ApolloError) => void) => {
onError: (fn: (param: ApolloError, context: OnErrorContext) => void) => {
off: () => void
}
}
Expand Down Expand Up @@ -119,9 +128,9 @@ export function useSubscription <
const optionsRef = paramToReactive(options)

const result = ref<TResult | null | undefined>()
const resultEvent = useEventHook<FetchResult<TResult>>()
const resultEvent = useEventHook<[FetchResult<TResult>, OnResultContext]>()
const error = ref<ApolloError | null>(null)
const errorEvent = useEventHook<ApolloError>()
const errorEvent = useEventHook<[ApolloError, OnErrorContext]>()

const loading = ref(false)
vm && trackSubscription(loading)
Expand All @@ -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<TResult, TVariables>({
query: currentDocument,
Expand All @@ -155,15 +168,19 @@ export function useSubscription <
function onNextResult (fetchResult: FetchResult<TResult>) {
result.value = fetchResult.data
loading.value = false
resultEvent.trigger(fetchResult)
resultEvent.trigger(fetchResult, {
client: getClient(),
})
}

function onError (fetchError: unknown) {
const apolloError = toApolloError(fetchError)

error.value = apolloError
loading.value = false
errorEvent.trigger(apolloError)
errorEvent.trigger(apolloError, {
client: getClient(),
})
}

function stop () {
Expand Down
12 changes: 6 additions & 6 deletions packages/vue-apollo-composable/src/util/useEventHook.ts
@@ -1,23 +1,23 @@
export function useEventHook<TParam = any> () {
const fns: Array<(param: TParam) => void> = []
export function useEventHook<TParams extends any[] = any[]> () {
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)
}
}

Expand Down

0 comments on commit 0be5d9b

Please sign in to comment.