Skip to main content

Loading and Stale Data

Render deterministic query states during SSR, argument changes, skips, and errors.

Use status for the main lifecycle and isStale when deliberately keeping an older result.

State meanings

StatusDataMeaning
idleUsually nullSkipped, anonymous required query, or cleared state
pendingnull or retained valueCurrent result is loading
successQuery resultCurrent result settled successfully
errorUsually nullCurrent execution failed

pending is equivalent to status === 'pending' for normal queries.

Render all states

vue
<template>
  <ProjectSkeleton v-if="status === 'pending' && !data" />

  <section v-else-if="status === 'error'">
    <p>Projects could not be loaded.</p>
    <button @click="refresh">Try again</button>
  </section>

  <p v-else-if="status === 'idle'">Select an organization.</p>

  <section v-else :aria-busy="isStale">
    <ProjectList :projects="data ?? []" />
    <p v-if="isStale">Updating results…</p>
  </section>
</template>

Do not treat !data as the only loading check. A valid query can return null, and a stale query can be pending while data remains visible.

Initial data

Use initialData for a presentation placeholder with the exact raw result shape:

ts
const projects = await useConvexQuery(
  api.projects.list,
  {},
  {
    initialData: [],
  },
)

Initial data is not a confirmed server result. The status still communicates whether the current query has settled.

Errors after previous data

Decide whether an old result remains useful when a refresh fails. The normal composable contract exposes the error and current data; the page should label degraded or stale content rather than presenting it as current without context.

Clear state

clear() removes visible data and error state. Use it for an explicit UI reset. Identity transitions clear identity-owned state automatically; application code should not race that process with a manual clear.