Skip to content

Refetching ​

Cached data goes stale. A query reads the cache and only reaches the network when it has to, which means a result can outlive the truth on the server.

Refetching is how you ask again on purpose. This page covers four ways to do it: re-running a single query on demand, polling on a fixed cadence, refetching after a mutation, and refetching across the whole app from outside a component.

Refetching a single query ​

Every useQuery exposes a refetch function:

vue
<script setup lang="ts">
const { 
breed
} =
defineProps
<{
breed
: string }>()
const {
current
,
refetch
} =
useQuery
(
gql
`
query GetDogPhoto($breed: String!) { dog(breed: $breed) { id photo } } `, {
variables
: () => ({
breed
}),
}) </script> <template> <
img
v-if="
current
.
resultState
=== 'complete'"
:
src
="
current
.
result
.
dog
.
photo
"
> <
button
@
click
="
refetch
()">
Refresh </
button
>
</template>

refetch() returns a promise that resolves with the new result.

refetch is handed to both #data and #error:

vue
<script setup lang="ts">
import { 
ApolloQuery
} from '@vue/apollo-components'
const {
breed
} =
defineProps
<{
breed
: string }>()
</script> <template>
<
ApolloQuery
:
query
=
"
GetDogPhoto
"
:
variables
=
"
{
breed
}
"
>
<template #
error
="{
error
,
refetch
}">
{{
error
.
message
}}
<
button
@
click
="
refetch
()">
Try again </
button
>
</template> <template #
data
="{
data
,
loading
,
refetch
}">
<
img
:
src
="
data
.
dog
.
photo
">
<
button
:
disabled
="
loading
" @
click
="
refetch
()">
Refresh </
button
>
</template> </ApolloQuery> </template>

refetch() returns a promise that resolves with the new result. #data keeps rendering throughout, with loading reporting the request in flight, so a refresh never blanks the image.

Refetch with different variables ​

You can pass new variables for a one-off refetch:

ts
const { 
refetch
,
variables
} =
useQuery
(
QUERY
, {
variables
: {
breed
: 'bulldog' },
}) // One-time refetch with different variables await
refetch
({
breed
: 'poodle' })
// The reactive variables ref does not change
console
.
log
(
variables
.
value
.
breed
) // 'bulldog'
vue
<script setup lang="ts">
import { 
ApolloQuery
} from '@vue/apollo-components'
</script> <template>
<
ApolloQuery
:
query
="
gql
`
query GetDogPhoto($breed: String!) { dog(breed: $breed) { id photo } } `"
:
variables
="{
breed
: 'bulldog' }"
> <template #
data
="{
data
,
refetch
}">
<
img
:
src
="
data
.
dog
.
photo
">
<!-- One-time refetch with different variables. The `variables` prop is untouched. --> <
button
@
click
="
refetch
({
breed
: 'poodle' })">
Show a poodle </
button
>
</template> </ApolloQuery> </template>

Variables passed to refetch are not persisted

refetch({ id: 2 }) uses those variables for that one request. The query's declared variables keep their previous value, and the next change to them, or another refetch with no arguments, goes back to using them.

If you want the new variables to stick, change the declared variables instead.

Polling ​

Set pollInterval (in milliseconds) to re-run a query at a fixed interval:

vue
<script setup lang="ts">
const { 
current
} =
useQuery
(
gql
`
query GetNotifications { notifications { id } } `, {
pollInterval
: 5000, // Every 5 seconds
}) </script>

Polling pauses when the query is stopped, when the component is unmounted, or when enabled flips to false. It resumes when the query becomes active again.

Set the pollInterval prop (in milliseconds) to re-run a query at a fixed interval:

template
<ApolloQuery :query="GetNotifications" :pollInterval="5000">

Polling pauses while the component is unmounted, while disabled is true, and after stop(). It resumes when the query becomes active again. Unmounting is all it takes to stop a poll:

template
<ApolloQuery v-if="panelOpen" :query="GetNotifications" :pollInterval="5000">

Skipping individual poll attempts ​

If you want polling to continue but occasionally skip a poll (for example, while a modal is open), use skipPollAttempt:

ts
const 
isModalOpen
=
ref
(false)
useQuery
(
QUERY
, {
pollInterval
: 5000,
skipPollAttempt
: () =>
isModalOpen
.
value
,
})

There is no prop for it, so pass it through options, which takes the whole useQuery.Options object:

template
<ApolloQuery
  :query="GetNotifications"
  :pollInterval="5000"
  :options="{ skipPollAttempt: () => isModalOpen }"
>

When skipPollAttempt returns true, that one poll is skipped. The next poll runs at the normal interval.

Imperative polling control ​

For full control, reach into the underlying ObservableQuery:

vue
<script setup lang="ts">
const { 
query
} =
useQuery
(
QUERY
)
// Start polling
query
.
value
?.
startPolling
(2000)
// Stop polling
query
.
value
?.
stopPolling
()
</script>

query is a ref to the underlying ObservableQuery. It is undefined while the query is disabled.

A template ref on <ApolloQuery> exposes the whole useQuery.Result, refs already unwrapped, including the underlying ObservableQuery:

vue
<script setup lang="ts">
import { ApolloQuery } from '@vue/apollo-components'
import { useTemplateRef } from 'vue'
import { GetNotifications } from './queries'

const notifications = useTemplateRef('notifications')

function pause() {
  notifications.value?.query?.stopPolling()
}
</script>

<template>
  <ApolloQuery ref="notifications" :query="GetNotifications" :pollInterval="5000">
    <template #data="{ data }">
      {{ data.notifications.length }}
      <button @click="pause()">
        Pause
      </button>
    </template>
  </ApolloQuery>
</template>

query is undefined while disabled is true.

Refetching after a mutation ​

A successful mutation often invalidates queries that display the same data. The simplest way to refresh those queries is to list them in refetchQueries:

ts
const { 
mutate
} =
useMutation
(
CREATE_TODO
, {
refetchQueries
: [
GET_TODOS
, // by document
'GetTodos', // or by operation name ], })
template
<ApolloMutation
  v-slot="{ mutate }"
  :mutation="CreateTodo"
  :options="{ refetchQueries: [GetTodos, 'GetTodos'] }"
  @error="console.error"
>
  <button @click="mutate({ variables: { text } })">
    Add
  </button>
</ApolloMutation>

refetchQueries accepts documents and operation names alike. Everything on useMutation.Options is reachable this way, including awaitRefetchQueries and onQueryUpdated below.

You can also pass:

  • 'active' to refetch every currently-active query in the app.
  • 'all' to refetch every query, active or inactive.
  • A function that receives the mutation result and returns an array.

Active vs inactive queries

An active query is one that has at least one subscriber (a mounted component or live composable). An inactive query has been cached but no longer has any live subscribers. Most of the time you want 'active', not 'all'.

Waiting for refetches to finish ​

By default, mutate resolves as soon as the mutation completes. The triggered refetches happen in parallel. If you want mutate to wait until the refetches finish too, set awaitRefetchQueries:

ts
const { mutate } = useMutation(CREATE_TODO, {
  refetchQueries: [GET_TODOS],
  awaitRefetchQueries: true,
})
template
<ApolloMutation
  :mutation="CreateTodo"
  :options="{ refetchQueries: [GetTodos], awaitRefetchQueries: true }"
>

onQueryUpdated ​

For finer control, onQueryUpdated intercepts each refetch attempt. Return false to skip, true to proceed, or a promise to wait for it:

ts
const { mutate } = useMutation(CREATE_TODO, {
  refetchQueries: [GET_TODOS],
  onQueryUpdated(observableQuery) {
    // Skip queries whose variables are not relevant
    if (observableQuery.queryName === 'SomeUnrelatedQuery') {
      return false
    }
    return observableQuery.refetch()
  },
})
vue
<script setup lang="ts">
import type { ObservableQuery } from '@apollo/client'
import { ApolloMutation } from '@vue/apollo-components'
import { CreateTodo, GetTodos } from './queries'

function onQueryUpdated(observableQuery: ObservableQuery) {
  // Skip queries whose variables are not relevant
  if (observableQuery.queryName === 'SomeUnrelatedQuery') {
    return false
  }
  return observableQuery.refetch()
}
</script>

<template>
  <ApolloMutation
    :mutation="CreateTodo"
    :options="{ refetchQueries: [GetTodos], onQueryUpdated }"
  />
</template>

This is also the way to refetch queries after an update callback modifies the cache. See Cache Updates for the full pattern.

Refetching outside components ​

For app-wide refetches (after a logout, after a successful purchase that touches many data sources), call client.refetchQueries directly:

ts
import { useApolloClient } from '@vue/apollo-composable'

const { client } = useApolloClient()

// Refetch by name or document
await client.refetchQueries({
  include: ['GetUser', GET_TODOS],
})

// Refetch every active query
await client.refetchQueries({ include: 'active' })

// Refetch queries that read a particular field
await client.refetchQueries({
  updateCache(cache) {
    cache.evict({ fieldName: 'currentUser' })
  },
})

The updateCache form is useful after a logout: evict the relevant cached fields, and refetchQueries automatically refetches the queries that observed them.

See the upstream client.refetchQueries reference for the full API.

Refetching vs other patterns ​

GoalTool
Pull fresh data once, on a user action (refresh button)refetch()
Stream updates continuously from the serverSubscriptions
Keep one query in sync at a fixed intervalpollInterval
Update queries after a mutationrefetchQueries on the mutation
Refetch many queries from any contextclient.refetchQueries(...)
Avoid the network entirelyDirect cache updates

Next steps ​

Released under the MIT License.