diff --git a/.changeset/strange-windows-repair.md b/.changeset/strange-windows-repair.md new file mode 100644 index 0000000000..3c1a3fc18a --- /dev/null +++ b/.changeset/strange-windows-repair.md @@ -0,0 +1,5 @@ +--- +'@urql/vue': patch +--- + +Refactor `useQuery` implementation to utilise the single-source implementation of `@urql/core@2.1.0`. This should improve the stability of promisified `useQuery()` calls and prevent operations from not being issued in some edge cases. diff --git a/packages/vue-urql/src/useQuery.ts b/packages/vue-urql/src/useQuery.ts index cdf0305f88..aabf77ea35 100644 --- a/packages/vue-urql/src/useQuery.ts +++ b/packages/vue-urql/src/useQuery.ts @@ -4,23 +4,7 @@ import { DocumentNode } from 'graphql'; import { WatchStopHandle, Ref, ref, watchEffect, reactive, isRef } from 'vue'; -import { - Source, - concat, - switchAll, - share, - fromValue, - makeSubject, - filter, - map, - pipe, - take, - publish, - onEnd, - onStart, - onPush, - toPromise, -} from 'wonka'; +import { Source, map, pipe, take, subscribe, onEnd, toPromise } from 'wonka'; import { Client, @@ -70,26 +54,6 @@ const watchOptions = { flush: 'pre' as const, }; -/** Wonka Operator to replay the most recent value to sinks */ -function replayOne(source: Source): Source { - let cached: undefined | T; - - return concat([ - pipe( - fromValue(cached!), - map(() => cached!), - filter(x => x !== undefined) - ), - pipe( - source, - onPush(value => { - cached = value; - }), - share - ), - ]); -} - export function useQuery( args: UseQueryArgs ): UseQueryResponse { @@ -118,10 +82,7 @@ export function callUseQuery( createRequest(args.query, args.variables as V) as any ); - const source: Ref>> = ref(null as any); - const next: Ref< - (query$: undefined | Source>) => void - > = ref(null as any); + const source: Ref | undefined> = ref(); stops.push( watchEffect(() => { @@ -132,6 +93,17 @@ export function callUseQuery( }, watchOptions) ); + stops.push( + watchEffect(() => { + source.value = !isPaused.value + ? client.executeQuery(request.value, { + requestPolicy: args.requestPolicy, + ...args.context, + }) + : undefined; + }, watchOptions) + ); + const state: UseQueryState = { data, stale, @@ -141,13 +113,11 @@ export function callUseQuery( fetching, isPaused, executeQuery(opts?: Partial): UseQueryResponse { - next.value( - client.executeQuery(request.value, { - requestPolicy: args.requestPolicy, - ...args.context, - ...opts, - }) - ); + source.value = client.executeQuery(request.value, { + requestPolicy: args.requestPolicy, + ...args.context, + ...opts, + }); return response; }, @@ -159,45 +129,34 @@ export function callUseQuery( }, }; - const getState = () => state; - stops.push( watchEffect( onInvalidate => { - const subject = makeSubject>(); - source.value = pipe(subject.source, replayOne); - next.value = (value: undefined | Source) => { - const query$ = pipe( - value - ? pipe( - value, - onStart(() => { - fetching.value = true; - stale.value = false; - }), - onPush(res => { - data.value = res.data; - stale.value = !!res.stale; - fetching.value = false; - error.value = res.error; - operation.value = res.operation; - extensions.value = res.extensions; - }), - share - ) - : fromValue(undefined), - onEnd(() => { - fetching.value = false; - stale.value = false; - }) + if (source.value) { + fetching.value = true; + stale.value = false; + + onInvalidate( + pipe( + source.value, + onEnd(() => { + fetching.value = false; + stale.value = false; + }), + subscribe(res => { + data.value = res.data; + stale.value = !!res.stale; + fetching.value = false; + error.value = res.error; + operation.value = res.operation; + extensions.value = res.extensions; + }) + ).unsubscribe ); - - subject.next(query$); - }; - - onInvalidate( - pipe(source.value, switchAll, map(getState), publish).unsubscribe - ); + } else { + fetching.value = false; + stale.value = false; + } }, { // NOTE: This part of the query pipeline is only initialised once and will need @@ -207,28 +166,17 @@ export function callUseQuery( ) ); - stops.push( - watchEffect(() => { - next.value( - !isPaused.value - ? client.executeQuery(request.value, { - requestPolicy: args.requestPolicy, - ...args.context, - }) - : undefined - ); - }, watchOptions) - ); - const response: UseQueryResponse = { ...state, then(onFulfilled, onRejected) { - return pipe( - source.value, - switchAll, - map(getState), - take(1), - toPromise + return (source.value + ? pipe( + source.value, + take(1), + map(() => state), + toPromise + ) + : Promise.resolve(state) ).then(onFulfilled, onRejected); }, }; diff --git a/packages/vue-urql/src/useSubscription.ts b/packages/vue-urql/src/useSubscription.ts index 77f3c2154b..90b30cea08 100644 --- a/packages/vue-urql/src/useSubscription.ts +++ b/packages/vue-urql/src/useSubscription.ts @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { DocumentNode } from 'graphql'; -import { Source, pipe, publish, share, onStart, onPush, onEnd } from 'wonka'; +import { Source, pipe, subscribe, onEnd } from 'wonka'; import { WatchStopHandle, Ref, ref, watchEffect, reactive, isRef } from 'vue'; @@ -98,32 +98,24 @@ export function callUseSubscription( stops.push( watchEffect(() => { - if (!isPaused.value) { - source.value = pipe( - client.executeSubscription(request.value, { - ...args.context, - }), - share - ); - } else { - source.value = undefined; - } + source.value = !isPaused.value + ? client.executeSubscription(request.value, { ...args.context }) + : undefined; }, watchOptions) ); stops.push( watchEffect(onInvalidate => { if (source.value) { + fetching.value = true; + onInvalidate( pipe( source.value, - onStart(() => { - fetching.value = true; - }), onEnd(() => { fetching.value = false; }), - onPush(result => { + subscribe(result => { fetching.value = true; (data.value = result.data !== undefined @@ -135,10 +127,11 @@ export function callUseSubscription( extensions.value = result.extensions; stale.value = !!result.stale; operation.value = result.operation; - }), - publish + }) ).unsubscribe ); + } else { + fetching.value = false; } }, watchOptions) ); @@ -154,13 +147,10 @@ export function callUseSubscription( executeSubscription( opts?: Partial ): UseSubscriptionState { - source.value = pipe( - client.executeSubscription(request.value, { - ...args.context, - ...opts, - }), - share - ); + source.value = client.executeSubscription(request.value, { + ...args.context, + ...opts, + }); return state; },