feat: new context params in event hook handlers

Allow easy access to the client, especially useful with useSubscription().
This commit is contained in:
Guillaume Chau
2023-11-29 14:58:09 +01:00
parent c9b648d4bd
commit 0be5d9b444
8 changed files with 118 additions and 35 deletions
+2 -2
View File
@@ -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).
+2 -2
View File
@@ -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).
+2 -2
View File
@@ -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).
@@ -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:
@@ -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<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
}
}
@@ -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()
@@ -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
}
+24 -7
View File
@@ -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<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>
@@ -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
}
}
@@ -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
@@ -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()
@@ -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,
@@ -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(),
})
})
}
@@ -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<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>
@@ -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
}
}
@@ -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)
@@ -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,
@@ -155,7 +168,9 @@ 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) {
@@ -163,7 +178,9 @@ export function useSubscription <
error.value = apolloError
loading.value = false
errorEvent.trigger(apolloError)
errorEvent.trigger(apolloError, {
client: getClient(),
})
}
function stop () {
@@ -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)
}
}