Skip to content

Loading States ​

Every operation exposes its own loading state, but real apps often want to combine them: a single indicator for the whole page, an app-wide loading bar, a "saving" toast that tracks every mutation. Vue Apollo ships six composables for aggregating loading state across multiple operations.

ScopeQueriesMutationsSubscriptions
Current effect scope (usually a component)useQueryLoadinguseMutationLoadinguseSubscriptionLoading
Whole appuseGlobalQueryLoadinguseGlobalMutationLoadinguseGlobalSubscriptionLoading

The per-scope composables track every useQuery/useMutation/useSubscription called within the current Vue effect scope (typically the component that calls them, plus anything they call recursively). The global composables track every operation in the entire app.

Components have their own scope

<ApolloQuery> runs its useQuery inside its own component instance, so a useQueryLoading() in the surrounding <script setup> will never see it. Reach for the global composables when the operations are declared as elements, or for the loading slot prop when you only care about one of them.

Everything on this page is written in the composables because these six are composables: there is no component form of an aggregate counter. They work the same in a file that otherwise uses only components.

Per-scope loading ​

vue
<script setup lang="ts">
const { current: users } = useQuery(GET_USERS)
const { current: posts } = useQuery(GET_POSTS)

// True while any query in this component is loading
const loading = useQueryLoading()
</script>

<template>
  <div v-if="loading">
    Loading...
  </div>
  <div v-else>
    <!-- Render data -->
  </div>
</template>

useQueryLoading returns a computed ref that is true while at least one query in the current scope is in flight. It is reactive: as queries finish, the ref flips back to false.

Use the same shape for mutations and subscriptions:

ts
const { mutate: updateUser } = useMutation(UPDATE_USER)
const saving = useMutationLoading()

Per-scope tracking is automatically scoped to the component that calls these composables. The same composable in a sibling component does not affect this scope.

Global loading ​

useGlobalQueryLoading, useGlobalMutationLoading, and useGlobalSubscriptionLoading track every operation in the entire app. Useful for app-shell loading indicators:

vue
<script setup lang="ts">
import { useGlobalQueryLoading } from '@vue/apollo-composable'

const loading = useGlobalQueryLoading()
</script>

<template>
  <header>
    <div v-if="loading" class="loading-bar" />
  </header>
  <main>
    <slot />
  </main>
</template>

Unlike the per-scope variants, the global composables can be called anywhere, including outside a component setup.

Restrictions ​

  • The per-scope composables must be called inside an effect scope (typically a <script setup> block, a Vue plugin, or anywhere getCurrentScope() returns a scope). They throw outside.
  • The global composables have no such restriction.

When to choose which ​

GoalComposable
Loading indicator for a specific component or sectionPer-scope: useQueryLoading, etc.
App-wide top loading barGlobal: useGlobalQueryLoading
Disable a button while a specific mutation is in flightThe loading of that mutation alone
Combine queries and mutations into one indicatorTwo refs, combined with `computed(() => queries.value

For the "indicator for one specific operation" case, do not reach for these composables.

Use the loading ref the original composable returned:

ts
const { mutate: save, loading } = useMutation(SAVE)
// `loading` is true while this specific save is running

Use the loading slot prop, which is scoped to that one element:

template
<ApolloMutation v-slot="{ mutate, loading }" :mutation="Save" @error="console.error">
  <button :disabled="loading" @click="mutate()">
    Save
  </button>
</ApolloMutation>

Combining categories ​

Want a single indicator that flips on while any operation is running? Compose them:

ts
const queries = useGlobalQueryLoading()
const mutations = useGlobalMutationLoading()
const subscriptions = useGlobalSubscriptionLoading()

const anyLoading = computed(
  () => queries.value || mutations.value || subscriptions.value,
)

SSR ​

The global composables avoid cross-request contamination during SSR. You can safely use them in components that render on the server.

The per-scope composables are also SSR-safe, but the counters are scoped to the current request's effect scope.

Next steps ​

  • Queries for the loading ref on individual useQuery calls.
  • Mutations for the loading ref on useMutation.
  • Suspense for a different approach to coarse-grained loading.

Released under the MIT License.