Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

(vue) - Implement client handles with methods to call hooks (#1599)

authored by

Phil Pluckthun and committed by
GitHub
e51daa5d fd9dcf62

+493 -161
+5
.changeset/beige-candles-kneel.md
··· 1 + --- 2 + '@urql/vue': minor 3 + --- 4 + 5 + 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
··· 1 + --- 2 + '@urql/vue': patch 3 + --- 4 + 5 + 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
··· 104 104 [Read more about how to use the `useSubscription` API on the "Subscriptions" 105 105 page.](../advanced/subscriptions.md#vue) 106 106 107 + ## useClientHandle 108 + 109 + The `useClientHandle()` function may, like the other `use*` functions, be called either in 110 + `setup()` or another lifecycle hook, and returns a so called "client handle". Using this `handle` we 111 + can access the [`Client`](./core.md#client) directly via the `client` property or call the other 112 + `use*` functions as methods, which will be directly bound to this `client`. This may be useful when 113 + chaining these methods inside an `async setup()` lifecycle function. 114 + 115 + | Method | Description | 116 + | ---------------------- | ------------------------------------------------------------------------------------------------------------------------- | 117 + | `client` | Contains the raw [`Client`](./core.md#client) reference, which allows the `Client` to be used directly. | 118 + | `useQuery(...)` | Accepts the same arguments as the `useQuery` function, but will always use the `Client` from the handle's context. | 119 + | `useMutation(...)` | Accepts the same arguments as the `useMutation` function, but will always use the `Client` from the handle's context. | 120 + | `useSubscription(...)` | Accepts the same arguments as the `useSubscription` function, but will always use the `Client` from the handle's context. | 121 + 107 122 ## Context API 108 123 109 124 In Vue the [`Client`](./core.md#client) is provided either to your app or to a parent component of a 110 125 given subtree and is then subsequently injected whenever one of the above composition functions is 111 126 used. 112 127 113 - You can manually retrieve the `Client` in your component by calling `useClient`. Symmetrically you 114 - can provide the `Client` from any of your components using the `provideClient` function. 128 + You can provide the `Client` from any of your components using the `provideClient` function. 115 129 Alternatively, `@urql/vue` also has a default export of a [Vue Plugin function](https://v3.vuejs.org/guide/plugins.html#using-a-plugin). 116 130 117 131 Both `provideClient` and the plugin function either accept an [instance of
+46
docs/basics/vue.md
··· 470 470 suspends this component will switch to using its `#fallback` template rather than its `#default` 471 471 template. 472 472 473 + ### Chaining calls in Vue Suspense 474 + 475 + As shown [above](#vue-suspense), in Vue Suspense the `async setup()` lifecycle function can be used 476 + to set up queries in advance, wait for them to have fetched some data, and then let the component 477 + render as usual. 478 + 479 + However, because the `async setup()` function can be used with `await`-ed promise calls, we may run 480 + into situations where we're trying to call functions like `useQuery()` after we've already awaited 481 + another promise and will be outside of the synchronous scope of the `setup()` lifecycle. This means 482 + that the `useQuery` (and `useSubscription` & `useMutation`) functions won't have access to the 483 + `Client` anymore that we'd have set up using `provideClient`. 484 + 485 + To prevent this, we can create something called a "client handle" using the `useClientHandle` 486 + function. 487 + 488 + ```js 489 + import { gql, useClientHandle } from '@urql/vue'; 490 + 491 + export default { 492 + async setup() { 493 + const handle = useClientHandle(); 494 + 495 + await Promise.resolve(); // NOTE: This could be any await call 496 + 497 + const result = await handle.useQuery({ 498 + query: gql` 499 + { 500 + todos { 501 + id 502 + title 503 + } 504 + } 505 + `, 506 + }); 507 + 508 + return { data: result.data }; 509 + }, 510 + }; 511 + ``` 512 + 513 + As we can see, when we use `handle.useQuery()` we're able to still create query results although we've 514 + interrupted the synchronous `setup()` lifecycle with a `Promise.resolve()` delay. This would also 515 + allow us to create chained queries by using 516 + [`computed`](https://v3.vuejs.org/guide/reactivity-computed-watchers.html#computed-values) to use an 517 + output from a preceding result in a next `handle.useQuery()` call. 518 + 473 519 ### Reading on 474 520 475 521 There are some more tricks we can use with `useQuery`. [Read more about its API in the API docs for
+1 -1
packages/vue-urql/example/package.json
··· 7 7 }, 8 8 "dependencies": { 9 9 "@urql/vue": "link:../", 10 - "vue": "^3.0.2" 10 + "vue": "^3.0.11" 11 11 }, 12 12 "devDependencies": { 13 13 "@vue/compiler-sfc": "^3.0.2",
+1 -1
packages/vue-urql/example/src/App.vue
··· 18 18 name: 'App', 19 19 setup() { 20 20 provideClient({ 21 - url: 'https://countries-274616.ew.r.appspot.com/', 21 + url: 'https://trygql.dev/graphql/basic-pokedex', 22 22 }); 23 23 }, 24 24 components: {
+44 -11
packages/vue-urql/example/src/components/HelloWorld.vue
··· 1 1 <template> 2 2 <div> 3 - <div v-if="data"> 4 - <pre>{{ JSON.stringify(data) }}</pre> 3 + <div v-if="pokemons"> 4 + <pre>{{ JSON.stringify(pokemons) }}</pre> 5 + </div> 6 + <div v-if="pokemon"> 7 + <pre>{{ JSON.stringify(pokemon) }}</pre> 5 8 </div> 9 + <button @click="nextPokemon">Next Pokemon</button> 6 10 </div> 7 11 </template> 8 12 9 13 <script> 10 - import { useQuery } from '@urql/vue'; 14 + import { ref, computed } from 'vue'; 15 + import { gql, useClientHandle } from '@urql/vue'; 11 16 12 17 export default { 13 18 async setup() { 14 - const query = ` 15 - query { 16 - Country { 17 - name 19 + const handle = useClientHandle(); 20 + 21 + const pokemons = await handle.useQuery({ 22 + query: gql` 23 + { 24 + pokemons(limit: 10) { 25 + id 26 + name 27 + } 28 + } 29 + ` 30 + }); 31 + 32 + const index = ref(0); 33 + 34 + const pokemon = await handle.useQuery({ 35 + query: gql` 36 + query ($id: ID!) { 37 + pokemon(id: $id) { 38 + id 39 + name 40 + } 18 41 } 19 - } 20 - `; 42 + `, 43 + variables: { 44 + id: computed(() => pokemons.data.value.pokemons[index.value].id), 45 + }, 46 + }); 21 47 22 - const result = useQuery({ query }); 23 - return { data: result.data }; 48 + return { 49 + pokemons: pokemons.data, 50 + pokemon: pokemon.data, 51 + nextPokemon() { 52 + index.value = index.value < (pokemons.data.value.pokemons.length - 1) 53 + ? index.value + 1 54 + : 0; 55 + }, 56 + }; 24 57 }, 25 58 name: 'HelloWorld', 26 59 }
+67 -4
packages/vue-urql/example/yarn.lock
··· 37 37 lodash "^4.17.19" 38 38 to-fast-properties "^2.0.0" 39 39 40 + "@graphql-typed-document-node/core@^3.1.0": 41 + version "3.1.0" 42 + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950" 43 + integrity sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg== 44 + 40 45 "@koa/cors@^3.1.0": 41 46 version "3.1.0" 42 47 resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.1.0.tgz#618bb073438cfdbd3ebd0e648a76e33b84f3a3b2" ··· 266 271 "@types/express-serve-static-core" "*" 267 272 "@types/mime" "*" 268 273 269 - "@urql/core@^1.13.1": 270 - version "1.13.1" 271 - resolved "https://registry.yarnpkg.com/@urql/core/-/core-1.13.1.tgz#7247c27dccd7570010de91730d1f16fd15892829" 272 - integrity sha512-Zl4UwvcE9JbWKzrtxnlmfF+rkX50GzK5dpMlB6FnUYF0sLmuGMxp67lnhTQsfTNJ+41bkj4lk0PMWEnG7KUsTw== 274 + "@urql/core@^2.0.0": 275 + version "2.0.0" 276 + resolved "https://registry.yarnpkg.com/@urql/core/-/core-2.0.0.tgz#b4936dd51fe84cb570506d9ee2579149689f7535" 277 + integrity sha512-Qj24CG8ullqZZsYmjrSH0JhH+nY7kj8GbVbA9si3KUjlYs75A/MBQU3i97j6oWyGldDBapyis2CfaQeXKbv8rA== 273 278 dependencies: 279 + "@graphql-typed-document-node/core" "^3.1.0" 274 280 wonka "^4.0.14" 275 281 276 282 "@urql/vue@link:..": 277 283 version "0.0.0" 278 284 uid "" 279 285 286 + "@vue/compiler-core@3.0.11": 287 + version "3.0.11" 288 + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.11.tgz#5ef579e46d7b336b8735228758d1c2c505aae69a" 289 + integrity sha512-6sFj6TBac1y2cWCvYCA8YzHJEbsVkX7zdRs/3yK/n1ilvRqcn983XvpBbnN3v4mZ1UiQycTvOiajJmOgN9EVgw== 290 + dependencies: 291 + "@babel/parser" "^7.12.0" 292 + "@babel/types" "^7.12.0" 293 + "@vue/shared" "3.0.11" 294 + estree-walker "^2.0.1" 295 + source-map "^0.6.1" 296 + 280 297 "@vue/compiler-core@3.0.2": 281 298 version "3.0.2" 282 299 resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.2.tgz#7790b7a1fcbba5ace4d81a70ce59096fa5c95734" ··· 287 304 "@vue/shared" "3.0.2" 288 305 estree-walker "^2.0.1" 289 306 source-map "^0.6.1" 307 + 308 + "@vue/compiler-dom@3.0.11": 309 + version "3.0.11" 310 + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.0.11.tgz#b15fc1c909371fd671746020ba55b5dab4a730ee" 311 + integrity sha512-+3xB50uGeY5Fv9eMKVJs2WSRULfgwaTJsy23OIltKgMrynnIj8hTYY2UL97HCoz78aDw1VDXdrBQ4qepWjnQcw== 312 + dependencies: 313 + "@vue/compiler-core" "3.0.11" 314 + "@vue/shared" "3.0.11" 290 315 291 316 "@vue/compiler-dom@3.0.2", "@vue/compiler-dom@^3.0.2": 292 317 version "3.0.2" ··· 326 351 "@vue/compiler-dom" "3.0.2" 327 352 "@vue/shared" "3.0.2" 328 353 354 + "@vue/reactivity@3.0.11": 355 + version "3.0.11" 356 + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.11.tgz#07b588349fd05626b17f3500cbef7d4bdb4dbd0b" 357 + integrity sha512-SKM3YKxtXHBPMf7yufXeBhCZ4XZDKP9/iXeQSC8bBO3ivBuzAi4aZi0bNoeE2IF2iGfP/AHEt1OU4ARj4ao/Xw== 358 + dependencies: 359 + "@vue/shared" "3.0.11" 360 + 329 361 "@vue/reactivity@3.0.2": 330 362 version "3.0.2" 331 363 resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.2.tgz#42ed5af6025b494a5e69b05169fcddf04eebfe77" 332 364 integrity sha512-GdRloNcBar4yqWGXOcba1t//j/WizwfthfPUYkjcIPHjYnA/vTEQYp0C9+ZjPdinv1WRK1BSMeN/xj31kQES4A== 333 365 dependencies: 334 366 "@vue/shared" "3.0.2" 367 + 368 + "@vue/runtime-core@3.0.11": 369 + version "3.0.11" 370 + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.0.11.tgz#c52dfc6acf3215493623552c1c2919080c562e44" 371 + integrity sha512-87XPNwHfz9JkmOlayBeCCfMh9PT2NBnv795DSbi//C/RaAnc/bGZgECjmkD7oXJ526BZbgk9QZBPdFT8KMxkAg== 372 + dependencies: 373 + "@vue/reactivity" "3.0.11" 374 + "@vue/shared" "3.0.11" 335 375 336 376 "@vue/runtime-core@3.0.2": 337 377 version "3.0.2" ··· 341 381 "@vue/reactivity" "3.0.2" 342 382 "@vue/shared" "3.0.2" 343 383 384 + "@vue/runtime-dom@3.0.11": 385 + version "3.0.11" 386 + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.0.11.tgz#7a552df21907942721feb6961c418e222a699337" 387 + integrity sha512-jm3FVQESY3y2hKZ2wlkcmFDDyqaPyU3p1IdAX92zTNeCH7I8zZ37PtlE1b9NlCtzV53WjB4TZAYh9yDCMIEumA== 388 + dependencies: 389 + "@vue/runtime-core" "3.0.11" 390 + "@vue/shared" "3.0.11" 391 + csstype "^2.6.8" 392 + 344 393 "@vue/runtime-dom@3.0.2": 345 394 version "3.0.2" 346 395 resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.0.2.tgz#9d166d03225558025d3d80f5039b646e0051b71c" ··· 349 398 "@vue/runtime-core" "3.0.2" 350 399 "@vue/shared" "3.0.2" 351 400 csstype "^2.6.8" 401 + 402 + "@vue/shared@3.0.11": 403 + version "3.0.11" 404 + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.11.tgz#20d22dd0da7d358bb21c17f9bde8628152642c77" 405 + integrity sha512-b+zB8A2so8eCE0JsxjL24J7vdGl8rzPQ09hZNhystm+KqSbKcAej1A+Hbva1rCMmTTqA+hFnUSDc5kouEo0JzA== 352 406 353 407 "@vue/shared@3.0.2": 354 408 version "3.0.2" ··· 2008 2062 slash "^3.0.0" 2009 2063 vue "^3.0.2" 2010 2064 ws "^7.3.1" 2065 + 2066 + vue@^3.0.11: 2067 + version "3.0.11" 2068 + resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.11.tgz#c82f9594cbf4dcc869241d4c8dd3e08d9a8f4b5f" 2069 + integrity sha512-3/eUi4InQz8MPzruHYSTQPxtM3LdZ1/S/BvaU021zBnZi0laRUyH6pfuE4wtUeLvI8wmUNwj5wrZFvbHUXL9dw== 2070 + dependencies: 2071 + "@vue/compiler-dom" "3.0.11" 2072 + "@vue/runtime-dom" "3.0.11" 2073 + "@vue/shared" "3.0.11" 2011 2074 2012 2075 vue@^3.0.2: 2013 2076 version "3.0.2"
+1 -1
packages/vue-urql/package.json
··· 52 52 }, 53 53 "devDependencies": { 54 54 "graphql": "^15.1.0", 55 - "vue": "^3.0.2" 55 + "vue": "^3.0.11" 56 56 }, 57 57 "peerDependencies": { 58 58 "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0",
+26 -4
packages/vue-urql/src/index.ts
··· 1 1 export * from '@urql/core'; 2 - export * from './useClient'; 3 - export * from './useQuery'; 4 - export * from './useMutation'; 5 - export * from './useSubscription'; 2 + 3 + export * from './useClientHandle'; 4 + export { install, provideClient } from './useClient'; 5 + 6 + export { 7 + useQuery, 8 + UseQueryArgs, 9 + UseQueryResponse, 10 + UseQueryState, 11 + } from './useQuery'; 12 + 13 + export { 14 + useSubscription, 15 + UseSubscriptionArgs, 16 + UseSubscriptionResponse, 17 + UseSubscriptionState, 18 + SubscriptionHandlerArg, 19 + SubscriptionHandler, 20 + } from './useSubscription'; 21 + 22 + export { 23 + useMutation, 24 + UseMutationResponse, 25 + UseMutationState, 26 + } from './useMutation'; 27 + 6 28 import { install } from './useClient'; 7 29 8 30 export default install;
+7 -1
packages/vue-urql/src/useClient.ts
··· 1 - import { App, inject, provide } from 'vue'; 1 + import { App, getCurrentInstance, inject, provide } from 'vue'; 2 2 import { Client, ClientOptions } from '@urql/core'; 3 3 4 4 export function provideClient(opts: ClientOptions | Client) { ··· 13 13 } 14 14 15 15 export function useClient(): Client { 16 + if (process.env.NODE_ENV !== 'production' && !getCurrentInstance()) { 17 + throw new Error( 18 + 'use* functions may only be called during the `setup()` or other lifecycle hooks.' 19 + ); 20 + } 21 + 16 22 const client = inject('$urql') as Client; 17 23 if (process.env.NODE_ENV !== 'production' && !client) { 18 24 throw new Error(
+104
packages/vue-urql/src/useClientHandle.ts
··· 1 + import { DocumentNode } from 'graphql'; 2 + import { Client, TypedDocumentNode } from '@urql/core'; 3 + import { 4 + WatchStopHandle, 5 + getCurrentInstance, 6 + onMounted, 7 + onBeforeUnmount, 8 + } from 'vue'; 9 + 10 + import { useClient } from './useClient'; 11 + 12 + import { callUseQuery, UseQueryArgs, UseQueryResponse } from './useQuery'; 13 + 14 + import { callUseMutation, UseMutationResponse } from './useMutation'; 15 + 16 + import { 17 + callUseSubscription, 18 + UseSubscriptionArgs, 19 + SubscriptionHandlerArg, 20 + UseSubscriptionResponse, 21 + } from './useSubscription'; 22 + 23 + export interface ClientHandle { 24 + client: Client; 25 + 26 + useQuery<T = any, V = object>( 27 + args: UseQueryArgs<T, V> 28 + ): UseQueryResponse<T, V>; 29 + 30 + useSubscription<T = any, R = T, V = object>( 31 + args: UseSubscriptionArgs<T, V>, 32 + handler?: SubscriptionHandlerArg<T, R> 33 + ): UseSubscriptionResponse<T, R, V>; 34 + 35 + useMutation<T = any, V = any>( 36 + query: TypedDocumentNode<T, V> | DocumentNode | string 37 + ): UseMutationResponse<T, V>; 38 + } 39 + 40 + export function useClientHandle(): ClientHandle { 41 + const client = useClient(); 42 + const stops: WatchStopHandle[] = []; 43 + 44 + onBeforeUnmount(() => { 45 + let stop: WatchStopHandle | void; 46 + while ((stop = stops.shift())) stop(); 47 + }); 48 + 49 + const handle: ClientHandle = { 50 + client, 51 + 52 + useQuery<T = any, V = object>( 53 + args: UseQueryArgs<T, V> 54 + ): UseQueryResponse<T, V> { 55 + return callUseQuery(args, client, stops); 56 + }, 57 + 58 + useSubscription<T = any, R = T, V = object>( 59 + args: UseSubscriptionArgs<T, V>, 60 + handler?: SubscriptionHandlerArg<T, R> 61 + ): UseSubscriptionResponse<T, R, V> { 62 + return callUseSubscription(args, handler, client, stops); 63 + }, 64 + 65 + useMutation<T = any, V = any>( 66 + query: TypedDocumentNode<T, V> | DocumentNode | string 67 + ): UseMutationResponse<T, V> { 68 + return callUseMutation(query, client); 69 + }, 70 + }; 71 + 72 + if (process.env.NODE_ENV !== 'production') { 73 + onMounted(() => { 74 + Object.assign(handle, { 75 + useQuery<T = any, V = object>( 76 + args: UseQueryArgs<T, V> 77 + ): UseQueryResponse<T, V> { 78 + if (process.env.NODE_ENV !== 'production' && !getCurrentInstance()) { 79 + throw new Error( 80 + '`handle.useQuery()` should only be called in the `setup()` or a lifecycle hook.' 81 + ); 82 + } 83 + 84 + return callUseQuery(args, client, stops); 85 + }, 86 + 87 + useSubscription<T = any, R = T, V = object>( 88 + args: UseSubscriptionArgs<T, V>, 89 + handler?: SubscriptionHandlerArg<T, R> 90 + ): UseSubscriptionResponse<T, R, V> { 91 + if (process.env.NODE_ENV !== 'production' && !getCurrentInstance()) { 92 + throw new Error( 93 + '`handle.useSubscription()` should only be called in the `setup()` or a lifecycle hook.' 94 + ); 95 + } 96 + 97 + return callUseSubscription(args, handler, client, stops); 98 + }, 99 + }); 100 + }); 101 + } 102 + 103 + return handle; 104 + }
+5 -8
packages/vue-urql/src/useMutation.test.ts
··· 1 - jest.mock('vue', () => { 2 - const vue = jest.requireActual('vue'); 1 + jest.mock('./useClient.ts', () => ({ 2 + __esModule: true, 3 + ...jest.requireActual('./useClient.ts'), 4 + useClient: () => client, 5 + })); 3 6 4 - return { 5 - __esModule: true, 6 - ...vue, 7 - inject: () => client, 8 - }; 9 - }); 10 7 import { makeSubject, pipe, take, toPromise } from 'wonka'; 11 8 import { createClient } from '@urql/core'; 12 9 import { useMutation } from './useMutation';
+9 -1
packages/vue-urql/src/useMutation.ts
··· 1 + /* eslint-disable react-hooks/rules-of-hooks */ 2 + 1 3 import { ref, Ref } from 'vue'; 2 4 import { DocumentNode } from 'graphql'; 3 5 4 6 import { 7 + Client, 5 8 TypedDocumentNode, 6 9 CombinedError, 7 10 Operation, ··· 29 32 export function useMutation<T = any, V = any>( 30 33 query: TypedDocumentNode<T, V> | DocumentNode | string 31 34 ): UseMutationResponse<T, V> { 32 - const client = useClient(); 35 + return callUseMutation(query); 36 + } 33 37 38 + export function callUseMutation<T = any, V = any>( 39 + query: TypedDocumentNode<T, V> | DocumentNode | string, 40 + client: Client = useClient() 41 + ): UseMutationResponse<T, V> { 34 42 const data: Ref<T | undefined> = ref(); 35 43 const stale: Ref<boolean> = ref(false); 36 44 const fetching: Ref<boolean> = ref(false);
+5 -9
packages/vue-urql/src/useQuery.test.ts
··· 1 - jest.mock('vue', () => { 2 - const vue = jest.requireActual('vue'); 3 - 4 - return { 5 - __esModule: true, 6 - ...vue, 7 - inject: () => client, 8 - }; 9 - }); 1 + jest.mock('./useClient.ts', () => ({ 2 + __esModule: true, 3 + ...jest.requireActual('./useClient.ts'), 4 + useClient: () => client, 5 + })); 10 6 11 7 import { pipe, makeSubject, fromValue, delay } from 'wonka'; 12 8 import { createClient } from '@urql/core';
+76 -59
packages/vue-urql/src/useQuery.ts
··· 1 - import { Ref, ref, watchEffect, reactive, isRef } from 'vue'; 1 + /* eslint-disable react-hooks/rules-of-hooks */ 2 + 2 3 import { DocumentNode } from 'graphql'; 4 + 5 + import { WatchStopHandle, Ref, ref, watchEffect, reactive, isRef } from 'vue'; 3 6 4 7 import { 5 8 Source, ··· 20 23 } from 'wonka'; 21 24 22 25 import { 26 + Client, 23 27 OperationResult, 24 28 TypedDocumentNode, 25 29 CombinedError, ··· 87 91 } 88 92 89 93 export function useQuery<T = any, V = object>( 90 - _args: UseQueryArgs<T, V> 94 + args: UseQueryArgs<T, V> 95 + ): UseQueryResponse<T, V> { 96 + return callUseQuery(args); 97 + } 98 + 99 + export function callUseQuery<T = any, V = object>( 100 + _args: UseQueryArgs<T, V>, 101 + client: Client = useClient(), 102 + stops: WatchStopHandle[] = [] 91 103 ): UseQueryResponse<T, V> { 92 104 const args = reactive(_args); 93 - const client = useClient(); 94 105 95 106 const data: Ref<T | undefined> = ref(); 96 107 const stale: Ref<boolean> = ref(false); ··· 112 123 (query$: undefined | Source<OperationResult<T, V>>) => void 113 124 > = ref(null as any); 114 125 115 - watchEffect(() => { 116 - const newRequest = createRequest<T, V>(args.query, args.variables as any); 117 - if (request.value.key !== newRequest.key) { 118 - request.value = newRequest; 119 - } 120 - }, watchOptions); 126 + stops.push( 127 + watchEffect(() => { 128 + const newRequest = createRequest<T, V>(args.query, args.variables as any); 129 + if (request.value.key !== newRequest.key) { 130 + request.value = newRequest; 131 + } 132 + }, watchOptions) 133 + ); 121 134 122 135 const state: UseQueryState<T, V> = { 123 136 data, ··· 148 161 149 162 const getState = () => state; 150 163 151 - watchEffect( 152 - onInvalidate => { 153 - const subject = makeSubject<Source<any>>(); 154 - source.value = pipe(subject.source, replayOne); 155 - next.value = (value: undefined | Source<any>) => { 156 - const query$ = pipe( 157 - value 158 - ? pipe( 159 - value, 160 - onStart(() => { 161 - fetching.value = true; 162 - stale.value = false; 163 - }), 164 - onPush(res => { 165 - data.value = res.data; 166 - stale.value = !!res.stale; 167 - fetching.value = false; 168 - error.value = res.error; 169 - operation.value = res.operation; 170 - extensions.value = res.extensions; 171 - }), 172 - share 173 - ) 174 - : fromValue(undefined), 175 - onEnd(() => { 176 - fetching.value = false; 177 - stale.value = false; 178 - }) 164 + stops.push( 165 + watchEffect( 166 + onInvalidate => { 167 + const subject = makeSubject<Source<any>>(); 168 + source.value = pipe(subject.source, replayOne); 169 + next.value = (value: undefined | Source<any>) => { 170 + const query$ = pipe( 171 + value 172 + ? pipe( 173 + value, 174 + onStart(() => { 175 + fetching.value = true; 176 + stale.value = false; 177 + }), 178 + onPush(res => { 179 + data.value = res.data; 180 + stale.value = !!res.stale; 181 + fetching.value = false; 182 + error.value = res.error; 183 + operation.value = res.operation; 184 + extensions.value = res.extensions; 185 + }), 186 + share 187 + ) 188 + : fromValue(undefined), 189 + onEnd(() => { 190 + fetching.value = false; 191 + stale.value = false; 192 + }) 193 + ); 194 + 195 + subject.next(query$); 196 + }; 197 + 198 + onInvalidate( 199 + pipe(source.value, switchAll, map(getState), publish).unsubscribe 179 200 ); 201 + }, 202 + { 203 + // NOTE: This part of the query pipeline is only initialised once and will need 204 + // to do so synchronously 205 + flush: 'sync', 206 + } 207 + ) 208 + ); 180 209 181 - subject.next(query$); 182 - }; 183 - 184 - onInvalidate( 185 - pipe(source.value, switchAll, map(getState), publish).unsubscribe 210 + stops.push( 211 + watchEffect(() => { 212 + next.value( 213 + !isPaused.value 214 + ? client.executeQuery<T, V>(request.value, { 215 + requestPolicy: args.requestPolicy, 216 + ...args.context, 217 + }) 218 + : undefined 186 219 ); 187 - }, 188 - { 189 - // NOTE: This part of the query pipeline is only initialised once and will need 190 - // to do so synchronously 191 - flush: 'sync', 192 - } 220 + }, watchOptions) 193 221 ); 194 - 195 - watchEffect(() => { 196 - next.value( 197 - !isPaused.value 198 - ? client.executeQuery<T, V>(request.value, { 199 - requestPolicy: args.requestPolicy, 200 - ...args.context, 201 - }) 202 - : undefined 203 - ); 204 - }, watchOptions); 205 222 206 223 const response: UseQueryResponse<T, V> = { 207 224 ...state,
+5 -8
packages/vue-urql/src/useSubscription.test.ts
··· 1 - jest.mock('vue', () => { 2 - const vue = jest.requireActual('vue'); 1 + jest.mock('./useClient.ts', () => ({ 2 + __esModule: true, 3 + ...jest.requireActual('./useClient.ts'), 4 + useClient: () => client, 5 + })); 3 6 4 - return { 5 - __esModule: true, 6 - ...vue, 7 - inject: () => client, 8 - }; 9 - }); 10 7 import { makeSubject } from 'wonka'; 11 8 import { createClient } from '@urql/core'; 12 9 import { useSubscription } from './useSubscription';
+69 -50
packages/vue-urql/src/useSubscription.ts
··· 1 - import { Ref, ref, watchEffect, reactive, isRef } from 'vue'; 1 + /* eslint-disable react-hooks/rules-of-hooks */ 2 + 2 3 import { DocumentNode } from 'graphql'; 3 4 import { Source, pipe, publish, share, onStart, onPush, onEnd } from 'wonka'; 4 5 6 + import { WatchStopHandle, Ref, ref, watchEffect, reactive, isRef } from 'vue'; 7 + 5 8 import { 9 + Client, 6 10 OperationResult, 7 11 TypedDocumentNode, 8 12 CombinedError, ··· 24 28 } 25 29 26 30 export type SubscriptionHandler<T, R> = (prev: R | undefined, data: T) => R; 31 + export type SubscriptionHandlerArg<T, R> = MaybeRef<SubscriptionHandler<T, R>>; 27 32 28 33 export interface UseSubscriptionState<T = any, R = T, V = object> { 29 34 fetching: Ref<boolean>; ··· 49 54 }; 50 55 51 56 export function useSubscription<T = any, R = T, V = object>( 57 + args: UseSubscriptionArgs<T, V>, 58 + handler?: SubscriptionHandlerArg<T, R> 59 + ): UseSubscriptionResponse<T, R, V> { 60 + return callUseSubscription(args, handler); 61 + } 62 + 63 + export function callUseSubscription<T = any, R = T, V = object>( 52 64 _args: UseSubscriptionArgs<T, V>, 53 - handler?: MaybeRef<SubscriptionHandler<T, R>> 65 + handler?: SubscriptionHandlerArg<T, R>, 66 + client: Client = useClient(), 67 + stops: WatchStopHandle[] = [] 54 68 ): UseSubscriptionResponse<T, R, V> { 55 69 const args = reactive(_args); 56 - const client = useClient(); 57 70 58 71 const data: Ref<R | undefined> = ref(); 59 72 const stale: Ref<boolean> = ref(false); ··· 74 87 75 88 const source: Ref<Source<OperationResult<T, V>> | undefined> = ref(); 76 89 77 - watchEffect(() => { 78 - const newRequest = createRequest<T, V>(args.query, args.variables as any); 79 - if (request.value.key !== newRequest.key) { 80 - request.value = newRequest; 81 - } 82 - }, watchOptions); 90 + stops.push( 91 + watchEffect(() => { 92 + const newRequest = createRequest<T, V>(args.query, args.variables as any); 93 + if (request.value.key !== newRequest.key) { 94 + request.value = newRequest; 95 + } 96 + }, watchOptions) 97 + ); 83 98 84 - watchEffect(() => { 85 - if (!isPaused.value) { 86 - source.value = pipe( 87 - client.executeSubscription<T, V>(request.value, { 88 - ...args.context, 89 - }), 90 - share 91 - ); 92 - } else { 93 - source.value = undefined; 94 - } 95 - }, watchOptions); 99 + stops.push( 100 + watchEffect(() => { 101 + if (!isPaused.value) { 102 + source.value = pipe( 103 + client.executeSubscription<T, V>(request.value, { 104 + ...args.context, 105 + }), 106 + share 107 + ); 108 + } else { 109 + source.value = undefined; 110 + } 111 + }, watchOptions) 112 + ); 96 113 97 - watchEffect(onInvalidate => { 98 - if (source.value) { 99 - onInvalidate( 100 - pipe( 101 - source.value, 102 - onStart(() => { 103 - fetching.value = true; 104 - }), 105 - onEnd(() => { 106 - fetching.value = false; 107 - }), 108 - onPush(result => { 109 - fetching.value = true; 110 - (data.value = 111 - result.data !== undefined 112 - ? typeof scanHandler.value === 'function' 113 - ? scanHandler.value(data.value as any, result.data!) 114 - : result.data 115 - : (result.data as any)), 116 - (error.value = result.error); 117 - extensions.value = result.extensions; 118 - stale.value = !!result.stale; 119 - operation.value = result.operation; 120 - }), 121 - publish 122 - ).unsubscribe 123 - ); 124 - } 125 - }, watchOptions); 114 + stops.push( 115 + watchEffect(onInvalidate => { 116 + if (source.value) { 117 + onInvalidate( 118 + pipe( 119 + source.value, 120 + onStart(() => { 121 + fetching.value = true; 122 + }), 123 + onEnd(() => { 124 + fetching.value = false; 125 + }), 126 + onPush(result => { 127 + fetching.value = true; 128 + (data.value = 129 + result.data !== undefined 130 + ? typeof scanHandler.value === 'function' 131 + ? scanHandler.value(data.value as any, result.data!) 132 + : result.data 133 + : (result.data as any)), 134 + (error.value = result.error); 135 + extensions.value = result.extensions; 136 + stale.value = !!result.stale; 137 + operation.value = result.operation; 138 + }), 139 + publish 140 + ).unsubscribe 141 + ); 142 + } 143 + }, watchOptions) 144 + ); 126 145 127 146 const state: UseSubscriptionState<T, R, V> = { 128 147 data,
+1 -1
yarn.lock
··· 15801 15801 resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" 15802 15802 integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== 15803 15803 15804 - vue@^3.0.2: 15804 + vue@^3.0.11: 15805 15805 version "3.0.11" 15806 15806 resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.11.tgz#c82f9594cbf4dcc869241d4c8dd3e08d9a8f4b5f" 15807 15807 integrity sha512-3/eUi4InQz8MPzruHYSTQPxtM3LdZ1/S/BvaU021zBnZi0laRUyH6pfuE4wtUeLvI8wmUNwj5wrZFvbHUXL9dw==