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.

at main 702 lines 22 kB view raw view rendered
1--- 2title: Vue Bindings 3order: 1 4--- 5 6# Vue 7 8## Getting started 9 10The `@urql/vue` bindings have been written with [Vue 113](https://github.com/vuejs/vue-next/releases/tag/v3.0.0) in mind and use Vue's newer [Composition 12API](https://v3.vuejs.org/guide/composition-api-introduction.html). This gives the `@urql/vue` 13bindings capabilities to be more easily integrated into your existing `setup()` functions. 14 15### Installation 16 17Installing `@urql/vue` is quick and no other packages are immediately necessary. 18 19```sh 20yarn add @urql/vue graphql 21# or 22npm install --save @urql/vue graphql 23``` 24 25Most libraries related to GraphQL also need the `graphql` package to be installed as a peer 26dependency, so that they can adapt to your specific versioning requirements. That's why we'll need 27to install `graphql` alongside `@urql/vue`. 28 29Both the `@urql/vue` and `graphql` packages follow [semantic versioning](https://semver.org) and 30all `@urql/vue` packages will define a range of compatible versions of `graphql`. Watch out 31for breaking changes in the future however, in which case your package manager may warn you about 32`graphql` being out of the defined peer dependency range. 33 34### Setting up the `Client` 35 36The `@urql/vue` package exports a `Client` class, which we can use to create 37the GraphQL client. This central `Client` manages all of our GraphQL requests and results. 38 39```js 40import { Client, cacheExchange, fetchExchange } from '@urql/vue'; 41 42const client = new Client({ 43 url: 'http://localhost:3000/graphql', 44 exchanges: [cacheExchange, fetchExchange], 45}); 46``` 47 48At the bare minimum we'll need to pass an API's `url` and `exchanges` when we create a `Client` 49to get started. 50 51Another common option is `fetchOptions`. This option allows us to customize the options that will be 52passed to `fetch` when a request is sent to the given API `url`. We may pass in an options object or 53a function returning an options object. 54 55In the following example we'll add a token to each `fetch` request that our `Client` sends to our 56GraphQL API. 57 58```js 59const client = new Client({ 60 url: 'http://localhost:3000/graphql', 61 exchanges: [cacheExchange, fetchExchange], 62 fetchOptions: () => { 63 const token = getToken(); 64 return { 65 headers: { authorization: token ? `Bearer ${token}` : '' }, 66 }; 67 }, 68}); 69``` 70 71### Providing the `Client` 72 73To make use of the `Client` in Vue we will have to provide from a parent component to its child 74components. This will share one `Client` with the rest of our app. In `@urql/vue` there are two 75different ways to achieve this. 76 77The first method is to use `@urql/vue`'s `provideClient` function. This must be called in any of 78your parent components and accepts either a `Client` directly or just the options that you'd pass to 79`Client`. 80 81```html 82<script> 83 import { Client, provideClient, cacheExchange, fetchExchange } from '@urql/vue'; 84 85 const client = new Client({ 86 url: 'http://localhost:3000/graphql', 87 exchanges: [cacheExchange, fetchExchange], 88 }); 89 90 provideClient(client); 91</script> 92``` 93 94Alternatively we may use the exported `install` function and treat `@urql/vue` as a plugin by 95importing its default export and using it [as a plugin](https://v3.vuejs.org/guide/plugins.html#using-a-plugin). 96 97```js 98import { createApp } from 'vue'; 99import Root from './App.vue'; 100import urql, { cacheExchange, fetchExchange } from '@urql/vue'; 101 102const app = createApp(Root); 103 104app.use(urql, { 105 url: 'http://localhost:3000/graphql', 106 exchanges: [cacheExchange, fetchExchange], 107}); 108 109app.mount('#app'); 110``` 111 112The plugin also accepts `Client`'s options or a `Client` as its inputs. 113 114## Queries 115 116We'll implement queries using the `useQuery` function from `@urql/vue`. 117 118### Run a first query 119 120For the following examples, we'll imagine that we're querying data from a GraphQL API that contains 121todo items. Let's dive right into it! 122 123```jsx 124<template> 125 <div v-if="fetching"> 126 Loading... 127 </div> 128 <div v-else-if="error"> 129 Oh no... {{error}} 130 </div> 131 <div v-else> 132 <ul v-if="data"> 133 <li v-for="todo in data.todos" :key="todo.id">{{ todo.title }}</li> 134 </ul> 135 </div> 136</template> 137 138<script> 139import { gql, useQuery } from '@urql/vue'; 140 141export default { 142 setup() { 143 const result = useQuery({ 144 query: gql` 145 { 146 todos { 147 id 148 title 149 } 150 } 151 ` 152 }); 153 154 return { 155 fetching: result.fetching, 156 data: result.data, 157 error: result.error, 158 }; 159 } 160}; 161</script> 162``` 163 164Here we have implemented our first GraphQL query to fetch todos. We see that `useQuery` accepts 165options and returns a result object. In this case we've set the `query` option to our GraphQL query. 166 167The result object contains several properties. The `fetching` field indicates whether we're currently 168loading data, `data` contains the actual `data` from the API's result, and `error` is set when either 169the request to the API has failed or when our API result contained some `GraphQLError`s, which 170we'll get into later on the ["Errors" page](./errors.md). 171 172All of these properties on the result are derived from the [shape of 173`OperationResult`](../api/core.md#operationresult) and are marked as [reactive 174](https://v3.vuejs.org/guide/reactivity-fundamentals.html), which means they may 175update while the query is running, which will automatically update your UI. 176 177### Variables 178 179Typically we'll also need to pass variables to our queries, for instance, if we are dealing with 180pagination. For this purpose `useQuery` also accepts a `variables` input, which we can 181use to supply variables to our query. 182 183```jsx 184<template> 185 ... 186</template> 187 188<script> 189import { gql, useQuery } from '@urql/vue'; 190 191export default { 192 props: ['from', 'limit'], 193 setup({ from, limit }) { 194 return useQuery({ 195 query: gql` 196 query ($from: Int!, $limit: Int!) { 197 todos(from: $from, limit: $limit) { 198 id 199 title 200 } 201 } 202 `, 203 variables: { from, limit } 204 }); 205 } 206}; 207</script> 208``` 209 210As when we're sending GraphQL queries manually using `fetch`, the variables will be attached to the 211`POST` request body that is sent to our GraphQL API. 212 213All inputs that are passed to `useQuery` may also be [reactive 214state](https://v3.vuejs.org/guide/reactivity-fundamentals.html). This means that both the inputs and 215outputs of `useQuery` are reactive and may change over time. 216 217```jsx 218<template> 219 <ul v-if="data"> 220 <li v-for="todo in data.todos" :key="todo.id">{{ todo.title }}</li> 221 </ul> 222 <button @click="from += 10">Next Page</button> 223</template> 224 225<script> 226import { gql, useQuery } from '@urql/vue'; 227 228export default { 229 setup() { 230 const from = ref(0); 231 232 const result = useQuery({ 233 query: gql` 234 query ($from: Int!, $limit: Int!) { 235 todos(from: $from, limit: $limit) { 236 id 237 title 238 } 239 } 240 `, 241 variables: { from, limit: 10 } 242 }); 243 244 return { 245 from, 246 data: result.data, 247 }; 248 } 249}; 250</script> 251``` 252 253### Pausing `useQuery` 254 255In some cases we may want `useQuery` to execute a query when a pre-condition has been met, and not 256execute the query otherwise. For instance, we may be building a form and want a validation query to 257only take place when a field has been filled out. 258 259Since with Vue 3's Composition API we won't just conditionally call `useQuery` we can instead pass a 260reactive `pause` input to `useQuery`. 261 262In the previous example we've defined a query with mandatory arguments. The `$from` and `$limit` 263variables have been defined to be non-nullable `Int!` values. 264 265Let's pause the query we've just written to not execute when these variables are empty, to 266prevent `null` variables from being executed. We can do this by computing `pause` to become `true` 267whenever these variables are falsy: 268 269```js 270import { reactive } from 'vue' 271import { gql, useQuery } from '@urql/vue'; 272 273export default { 274 props: ['from', 'limit'], 275 setup({ from, limit }) { 276 const shouldPause = computed(() => from == null || limit == null); 277 return useQuery({ 278 query: gql` 279 query ($from: Int!, $limit: Int!) { 280 todos(from: $from, limit: $limit) { 281 id 282 title 283 } 284 } 285 `, 286 variables: { from, limit }, 287 pause: shouldPause 288 }); 289 } 290}; 291</script> 292``` 293 294Now whenever the mandatory `$from` or `$limit` variables aren't supplied the query won't be executed. 295This also means that `result.data` won't change, which means we'll still have access to our old data 296even though the variables may have changed. 297 298It's worth noting that depending on whether `from` and `limit` are reactive or not you may have to 299change how `pause` is computed. But there's also an imperative alternative to this API. Not only 300does the result you get back from `useQuery` have an `isPaused` ref, it also has `pause()` and 301`resume()` methods. 302 303```jsx 304<template> 305 <div v-if="fetching"> 306 Loading... 307 </div> 308 <button @click="isPaused ? resume() : pause()">Toggle Query</button> 309</template> 310 311<script> 312import { gql, useQuery } from '@urql/vue'; 313 314export default { 315 setup() { 316 return useQuery({ 317 query: gql` 318 { 319 todos { 320 id 321 title 322 } 323 } 324 ` 325 }); 326 } 327}; 328</script> 329``` 330 331This means that no matter whether you're in or outside of `setup()` or rather supplying the inputs 332to `useQuery` or using the outputs, you'll have access to ways to pause or unpause the query. 333 334### Request Policies 335 336As has become clear in the previous sections of this page, the `useQuery` hook accepts more options 337than just `query` and `variables`. Another option we should touch on is `requestPolicy`. 338 339The `requestPolicy` option determines how results are retrieved from our `Client`'s cache. By 340default this is set to `cache-first`, which means that we prefer to get results from our cache, but 341are falling back to sending an API request. 342 343Request policies aren't specific to `urql`'s Vue bindings, but are a common feature in its core. 344[You can learn more about how the cache behaves given the four different policies on the "Document 345Caching" page.](../basics/document-caching.md) 346 347```js 348import { useQuery } from '@urql/vue'; 349 350export default { 351 setup() { 352 return useQuery({ 353 query: TodosQuery, 354 requestPolicy: 'cache-and-network', 355 }); 356 }, 357}; 358``` 359 360Specifically, a new request policy may be passed directly to `useQuery` as an option. 361This policy is then used for this specific query. In this case, `cache-and-network` is used and 362the query will be refreshed from our API even after our cache has given us a cached result. 363 364Internally, the `requestPolicy` is just one of several "**context** options". The `context` 365provides metadata apart from the usual `query` and `variables` we may pass. This means that 366we may also change the `Client`'s default `requestPolicy` by passing it there. 367 368```js 369import { Client, cacheExchange, fetchExchange } from '@urql/vue'; 370 371const client = new Client({ 372 url: 'http://localhost:3000/graphql', 373 exchanges: [cacheExchange, fetchExchange], 374 // every operation will by default use cache-and-network rather 375 // than cache-first now: 376 requestPolicy: 'cache-and-network', 377}); 378``` 379 380### Context Options 381 382As mentioned, the `requestPolicy` option on `useQuery` is a part of `urql`'s context options. 383In fact, there are several more built-in context options, and the `requestPolicy` option is 384one of them. Another option we've already seen is the `url` option, which determines our 385API's URL. These options aren't limited to the `Client` and may also be passed per query. 386 387```jsx 388import { useQuery } from '@urql/vue'; 389 390export default { 391 setup() { 392 return useQuery({ 393 query: TodosQuery, 394 context: { 395 requestPolicy: 'cache-and-network', 396 url: 'http://localhost:3000/graphql?debug=true', 397 }, 398 }); 399 }, 400}; 401``` 402 403As we can see, the `context` property for `useQuery` accepts any known `context` option and can be 404used to alter them per query rather than globally. The `Client` accepts a subset of `context` 405options, while the `useQuery` option does the same for a single query. 406[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext) 407 408### Reexecuting Queries 409 410The `useQuery` hook updates and executes queries whenever its inputs, like the `query` or 411`variables` change, but in some cases we may find that we need to programmatically trigger a new 412query. This is the purpose of the `executeQuery` method which is a method on the result object 413that `useQuery` returns. 414 415Triggering a query programmatically may be useful in a couple of cases. It can for instance be used 416to refresh data that is currently being displayed. In these cases we may also override the 417`requestPolicy` of our query just once and set it to `network-only` to skip the cache. 418 419```js 420import { gql, useQuery } from '@urql/vue'; 421 422export default { 423 setup() { 424 const result = useQuery({ 425 query: gql` 426 { 427 todos { 428 id 429 title 430 } 431 } 432 `, 433 }); 434 435 return { 436 data: result.data, 437 fetching: result.fetching, 438 error: result.error, 439 refresh() { 440 result.executeQuery({ 441 requestPolicy: 'network-only', 442 }); 443 }, 444 }; 445 }, 446}; 447``` 448 449Calling `refresh` in the above example will execute the query again forcefully, and will skip the 450cache, since we're passing `requestPolicy: 'network-only'`. 451 452Furthermore the `executeQuery` function can also be used to programmatically start a query even 453when `pause` is set to `true`, which would usually stop all automatic queries. This can be used to 454perform one-off actions, or to set up polling. 455 456### Vue Suspense 457 458In Vue 3 a [new feature was introduced](https://vuedose.tips/go-async-in-vue-3-with-suspense/) that 459natively allows components to suspend while data is loading, which works universally on the server 460and on the client, where a replacement loading template is rendered on a parent while data is 461loading. 462 463Any component's `setup()` function can be updated to instead be an `async setup()` function, in 464other words, to return a `Promise` instead of directly returning its data. This means that we can 465update any `setup()` function to make use of Suspense. 466 467The `useQuery`'s returned result supports this, since it is a `PromiseLike`. We can update one of 468our examples to have a suspending component by changing our usage of `useQuery`: 469 470```jsx 471<template> 472 <ul> 473 <li v-for="todo in data.todos" :key="todo.id">{{ todo.title }}</li> 474 </ul> 475</template> 476 477<script> 478import { gql, useQuery } from '@urql/vue'; 479 480export default { 481 async setup() { 482 const { data, error } = await useQuery({ 483 query: gql` 484 { 485 todos { 486 id 487 title 488 } 489 } 490 ` 491 }); 492 493 return { data }; 494 } 495}; 496</script> 497``` 498 499As we can see, `await useQuery(...)` here suspends the component and what we render will not have to 500handle the loading states of `useQuery` at all. Instead in Vue Suspense we'll have to wrap a parent 501component in a "Suspense boundary." This boundary is what switches a parent to a loading state while 502parts of its children are fetching data. The suspense promise is in essence "bubbling up" until it 503finds a "Suspense boundary". 504 505``` 506<template> 507 <Suspense> 508 <template #default> 509 <MyAsyncComponent /> 510 </template> 511 <template #fallback> 512 <span>Loading...</span> 513 </template> 514 </Suspense> 515</template> 516``` 517 518As long as any parent component is wrapping our component which uses `async setup()` in this 519boundary, we'll get Vue Suspense to work correctly and trigger this loading state. When a child 520suspends this component will switch to using its `#fallback` template rather than its `#default` 521template. 522 523### Chaining calls in Vue Suspense 524 525As shown [above](#vue-suspense), in Vue Suspense the `async setup()` lifecycle function can be used 526to set up queries in advance, wait for them to have fetched some data, and then let the component 527render as usual. 528 529However, because the `async setup()` function can be used with `await`-ed promise calls, we may run 530into situations where we're trying to call functions like `useQuery()` after we've already awaited 531another promise and will be outside of the synchronous scope of the `setup()` lifecycle. This means 532that the `useQuery` (and `useSubscription` & `useMutation`) functions won't have access to the 533`Client` anymore that we'd have set up using `provideClient`. 534 535To prevent this, we can create something called a "client handle" using the `useClientHandle` 536function. 537 538```js 539import { gql, useClientHandle } from '@urql/vue'; 540 541export default { 542 async setup() { 543 const handle = useClientHandle(); 544 545 await Promise.resolve(); // NOTE: This could be any await call 546 547 const result = await handle.useQuery({ 548 query: gql` 549 { 550 todos { 551 id 552 title 553 } 554 } 555 `, 556 }); 557 558 return { data: result.data }; 559 }, 560}; 561``` 562 563As we can see, when we use `handle.useQuery()` we're able to still create query results although we've 564interrupted the synchronous `setup()` lifecycle with a `Promise.resolve()` delay. This would also 565allow us to create chained queries by using 566[`computed`](https://v3.vuejs.org/guide/reactivity-computed-watchers.html#computed-values) to use an 567output from a preceding result in a next `handle.useQuery()` call. 568 569### Reading on 570 571There are some more tricks we can use with `useQuery`. [Read more about its API in the API docs for 572it.](../api/vue.md#usequery) 573 574## Mutations 575 576The `useMutation` function is similar to `useQuery` but is triggered manually and accepts 577only a `DocumentNode` or `string` as an input. 578 579### Sending a mutation 580 581Let's again pick up an example with an imaginary GraphQL API for todo items, and dive into an 582example! We'll set up a mutation that _updates_ a todo item's title. 583 584```js 585import { gql, useMutation } from '@urql/vue'; 586 587export default { 588 setup() { 589 const { executeMutation: updateTodo } = useMutation(gql` 590 mutation ($id: ID!, $title: String!) { 591 updateTodo(id: $id, title: $title) { 592 id 593 title 594 } 595 } 596 `); 597 598 return { updateTodo }; 599 }, 600}; 601``` 602 603Similar to the `useQuery` output, `useMutation` returns a result object, which reflects the data of 604an executed mutation. That means it'll contain the familiar `fetching`, `error`, and `data` 605properties — it's identical since this is a common pattern of how `urql` 606presents [operation results](../api/core.md#operationresult). 607 608Unlike the `useQuery` hook, the `useMutation` hook doesn't execute automatically. At this point in 609our example, no mutation will be performed. To execute our mutation we instead have to call the 610`executeMutation` method on the result with some variables. 611 612### Using the mutation result 613 614When calling our `updateTodo` function we have two ways of getting to the result as it comes back 615from our API. We can either use the result itself, since all properties related to the last 616[operation result](../api/core.md#operationresult) are marked as [reactive 617](https://v3.vuejs.org/guide/reactivity-fundamentals.html) — or we can use the promise that the 618`executeMutation` method returns when it's called: 619 620```js 621import { gql, useMutation } from '@urql/vue'; 622 623export default { 624 setup() { 625 const updateTodoResult = useMutation(gql` 626 mutation ($id: ID!, $title: String!) { 627 updateTodo(id: $id, title: $title) { 628 id 629 title 630 } 631 } 632 `); 633 634 return { 635 updateTodo(id, title) { 636 const variables = { id, title: title || '' }; 637 updateTodoResult.executeMutation(variables).then(result => { 638 // The result is almost identical to `updateTodoResult` with the exception 639 // of `result.fetching` not being set and its properties not being reactive. 640 // It is an OperationResult. 641 }); 642 }, 643 }; 644 }, 645}; 646``` 647 648The reactive result that `useMutation` returns is useful when your UI has to display progress or 649results on the mutation, and the returned promise is particularly useful when you're adding 650side-effects that run after the mutation has completed. 651 652### Handling mutation errors 653 654It's worth noting that the promise we receive when calling the execute function will never 655reject. Instead it will always return a promise that resolves to a result. 656 657If you're checking for errors, you should use `result.error` instead, which will be set 658to a `CombinedError` when any kind of errors occurred while executing your mutation. 659[Read more about errors on our "Errors" page.](./errors.md) 660 661```js 662import { gql, useMutation } from '@urql/vue'; 663 664export default { 665 setup() { 666 const updateTodoResult = useMutation(gql` 667 mutation ($id: ID!, $title: String!) { 668 updateTodo(id: $id, title: $title) { 669 id 670 title 671 } 672 } 673 `); 674 675 return { 676 updateTodo(id, title) { 677 const variables = { id, title: title || '' }; 678 updateTodoResult.executeMutation(variables).then(result => { 679 if (result.error) { 680 console.error('Oh no!', result.error); 681 } 682 }); 683 }, 684 }; 685 }, 686}; 687``` 688 689There are some more tricks we can use with `useMutation`.<br /> 690[Read more about its API in the API docs for it.](../api/vue.md#usemutation) 691 692## Reading on 693 694This concludes the introduction for using `urql` with Vue. The rest of the documentation 695is mostly framework-agnostic and will apply to either `urql` in general or the `@urql/core` package, 696which is the same between all framework bindings. Hence, next we may want to learn more about one of 697the following to learn more about the internals: 698 699- [How does the default "document cache" work?](./document-caching.md) 700- [How are errors handled and represented?](./errors.md) 701- [A quick overview of `urql`'s architecture and structure.](../architecture.md) 702- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)