···11+---
22+'@urql/vue': minor
33+---
44+55+A `useClientHandle()` function has been added. This creates a `handle` on which all `use*` hooks can be called, like `await handle.useQuery(...)` or `await handle.useSubscription(...)` which is useful for sequentially chaining hook calls in an `async setup()` function or preserve the right instance of a `Client` across lifecycle hooks.
+5
.changeset/fluffy-ligers-draw.md
···11+---
22+'@urql/vue': patch
33+---
44+55+The `useClient()` function will now throw a more helpful error when it's called outside of any lifecycle hooks.
+16-2
docs/api/vue.md
···104104[Read more about how to use the `useSubscription` API on the "Subscriptions"
105105page.](../advanced/subscriptions.md#vue)
106106107107+## useClientHandle
108108+109109+The `useClientHandle()` function may, like the other `use*` functions, be called either in
110110+`setup()` or another lifecycle hook, and returns a so called "client handle". Using this `handle` we
111111+can access the [`Client`](./core.md#client) directly via the `client` property or call the other
112112+`use*` functions as methods, which will be directly bound to this `client`. This may be useful when
113113+chaining these methods inside an `async setup()` lifecycle function.
114114+115115+| Method | Description |
116116+| ---------------------- | ------------------------------------------------------------------------------------------------------------------------- |
117117+| `client` | Contains the raw [`Client`](./core.md#client) reference, which allows the `Client` to be used directly. |
118118+| `useQuery(...)` | Accepts the same arguments as the `useQuery` function, but will always use the `Client` from the handle's context. |
119119+| `useMutation(...)` | Accepts the same arguments as the `useMutation` function, but will always use the `Client` from the handle's context. |
120120+| `useSubscription(...)` | Accepts the same arguments as the `useSubscription` function, but will always use the `Client` from the handle's context. |
121121+107122## Context API
108123109124In Vue the [`Client`](./core.md#client) is provided either to your app or to a parent component of a
110125given subtree and is then subsequently injected whenever one of the above composition functions is
111126used.
112127113113-You can manually retrieve the `Client` in your component by calling `useClient`. Symmetrically you
114114-can provide the `Client` from any of your components using the `provideClient` function.
128128+You can provide the `Client` from any of your components using the `provideClient` function.
115129Alternatively, `@urql/vue` also has a default export of a [Vue Plugin function](https://v3.vuejs.org/guide/plugins.html#using-a-plugin).
116130117131Both `provideClient` and the plugin function either accept an [instance of
+46
docs/basics/vue.md
···470470suspends this component will switch to using its `#fallback` template rather than its `#default`
471471template.
472472473473+### Chaining calls in Vue Suspense
474474+475475+As shown [above](#vue-suspense), in Vue Suspense the `async setup()` lifecycle function can be used
476476+to set up queries in advance, wait for them to have fetched some data, and then let the component
477477+render as usual.
478478+479479+However, because the `async setup()` function can be used with `await`-ed promise calls, we may run
480480+into situations where we're trying to call functions like `useQuery()` after we've already awaited
481481+another promise and will be outside of the synchronous scope of the `setup()` lifecycle. This means
482482+that the `useQuery` (and `useSubscription` & `useMutation`) functions won't have access to the
483483+`Client` anymore that we'd have set up using `provideClient`.
484484+485485+To prevent this, we can create something called a "client handle" using the `useClientHandle`
486486+function.
487487+488488+```js
489489+import { gql, useClientHandle } from '@urql/vue';
490490+491491+export default {
492492+ async setup() {
493493+ const handle = useClientHandle();
494494+495495+ await Promise.resolve(); // NOTE: This could be any await call
496496+497497+ const result = await handle.useQuery({
498498+ query: gql`
499499+ {
500500+ todos {
501501+ id
502502+ title
503503+ }
504504+ }
505505+ `,
506506+ });
507507+508508+ return { data: result.data };
509509+ },
510510+};
511511+```
512512+513513+As we can see, when we use `handle.useQuery()` we're able to still create query results although we've
514514+interrupted the synchronous `setup()` lifecycle with a `Promise.resolve()` delay. This would also
515515+allow us to create chained queries by using
516516+[`computed`](https://v3.vuejs.org/guide/reactivity-computed-watchers.html#computed-values) to use an
517517+output from a preceding result in a next `handle.useQuery()` call.
518518+473519### Reading on
474520475521There are some more tricks we can use with `useQuery`. [Read more about its API in the API docs for
···11export * from '@urql/core';
22-export * from './useClient';
33-export * from './useQuery';
44-export * from './useMutation';
55-export * from './useSubscription';
22+33+export * from './useClientHandle';
44+export { install, provideClient } from './useClient';
55+66+export {
77+ useQuery,
88+ UseQueryArgs,
99+ UseQueryResponse,
1010+ UseQueryState,
1111+} from './useQuery';
1212+1313+export {
1414+ useSubscription,
1515+ UseSubscriptionArgs,
1616+ UseSubscriptionResponse,
1717+ UseSubscriptionState,
1818+ SubscriptionHandlerArg,
1919+ SubscriptionHandler,
2020+} from './useSubscription';
2121+2222+export {
2323+ useMutation,
2424+ UseMutationResponse,
2525+ UseMutationState,
2626+} from './useMutation';
2727+628import { install } from './useClient';
729830export default install;
+7-1
packages/vue-urql/src/useClient.ts
···11-import { App, inject, provide } from 'vue';
11+import { App, getCurrentInstance, inject, provide } from 'vue';
22import { Client, ClientOptions } from '@urql/core';
3344export function provideClient(opts: ClientOptions | Client) {
···1313}
14141515export function useClient(): Client {
1616+ if (process.env.NODE_ENV !== 'production' && !getCurrentInstance()) {
1717+ throw new Error(
1818+ 'use* functions may only be called during the `setup()` or other lifecycle hooks.'
1919+ );
2020+ }
2121+1622 const client = inject('$urql') as Client;
1723 if (process.env.NODE_ENV !== 'production' && !client) {
1824 throw new Error(
+104
packages/vue-urql/src/useClientHandle.ts
···11+import { DocumentNode } from 'graphql';
22+import { Client, TypedDocumentNode } from '@urql/core';
33+import {
44+ WatchStopHandle,
55+ getCurrentInstance,
66+ onMounted,
77+ onBeforeUnmount,
88+} from 'vue';
99+1010+import { useClient } from './useClient';
1111+1212+import { callUseQuery, UseQueryArgs, UseQueryResponse } from './useQuery';
1313+1414+import { callUseMutation, UseMutationResponse } from './useMutation';
1515+1616+import {
1717+ callUseSubscription,
1818+ UseSubscriptionArgs,
1919+ SubscriptionHandlerArg,
2020+ UseSubscriptionResponse,
2121+} from './useSubscription';
2222+2323+export interface ClientHandle {
2424+ client: Client;
2525+2626+ useQuery<T = any, V = object>(
2727+ args: UseQueryArgs<T, V>
2828+ ): UseQueryResponse<T, V>;
2929+3030+ useSubscription<T = any, R = T, V = object>(
3131+ args: UseSubscriptionArgs<T, V>,
3232+ handler?: SubscriptionHandlerArg<T, R>
3333+ ): UseSubscriptionResponse<T, R, V>;
3434+3535+ useMutation<T = any, V = any>(
3636+ query: TypedDocumentNode<T, V> | DocumentNode | string
3737+ ): UseMutationResponse<T, V>;
3838+}
3939+4040+export function useClientHandle(): ClientHandle {
4141+ const client = useClient();
4242+ const stops: WatchStopHandle[] = [];
4343+4444+ onBeforeUnmount(() => {
4545+ let stop: WatchStopHandle | void;
4646+ while ((stop = stops.shift())) stop();
4747+ });
4848+4949+ const handle: ClientHandle = {
5050+ client,
5151+5252+ useQuery<T = any, V = object>(
5353+ args: UseQueryArgs<T, V>
5454+ ): UseQueryResponse<T, V> {
5555+ return callUseQuery(args, client, stops);
5656+ },
5757+5858+ useSubscription<T = any, R = T, V = object>(
5959+ args: UseSubscriptionArgs<T, V>,
6060+ handler?: SubscriptionHandlerArg<T, R>
6161+ ): UseSubscriptionResponse<T, R, V> {
6262+ return callUseSubscription(args, handler, client, stops);
6363+ },
6464+6565+ useMutation<T = any, V = any>(
6666+ query: TypedDocumentNode<T, V> | DocumentNode | string
6767+ ): UseMutationResponse<T, V> {
6868+ return callUseMutation(query, client);
6969+ },
7070+ };
7171+7272+ if (process.env.NODE_ENV !== 'production') {
7373+ onMounted(() => {
7474+ Object.assign(handle, {
7575+ useQuery<T = any, V = object>(
7676+ args: UseQueryArgs<T, V>
7777+ ): UseQueryResponse<T, V> {
7878+ if (process.env.NODE_ENV !== 'production' && !getCurrentInstance()) {
7979+ throw new Error(
8080+ '`handle.useQuery()` should only be called in the `setup()` or a lifecycle hook.'
8181+ );
8282+ }
8383+8484+ return callUseQuery(args, client, stops);
8585+ },
8686+8787+ useSubscription<T = any, R = T, V = object>(
8888+ args: UseSubscriptionArgs<T, V>,
8989+ handler?: SubscriptionHandlerArg<T, R>
9090+ ): UseSubscriptionResponse<T, R, V> {
9191+ if (process.env.NODE_ENV !== 'production' && !getCurrentInstance()) {
9292+ throw new Error(
9393+ '`handle.useSubscription()` should only be called in the `setup()` or a lifecycle hook.'
9494+ );
9595+ }
9696+9797+ return callUseSubscription(args, handler, client, stops);
9898+ },
9999+ });
100100+ });
101101+ }
102102+103103+ return handle;
104104+}