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 same-identity result.
State meanings
| Status | Data | Meaning |
|---|---|---|
idle | undefined | Skipped or anonymous required query |
pending | undefined or retained value | Current result is loading |
success | Query result, including null | Current result settled successfully |
error | Usually undefined | Current execution failed |
pending is exactly equivalent to status === 'pending'.
Render all states
<template>
<ProjectSkeleton v-if="status === 'pending' && data === undefined" />
<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 use a truthiness check as the loading contract. A successful query can return null, while a stale query can be pending with data still visible.
Presentation fallbacks
Keep placeholders in presentation code so query state continues to describe only real query results:
const projects = useConvexQuery(api.projects.list, {})
const visibleProjects = computed(() => projects.data.value ?? [])Errors after previous data
When a refresh fails, use error and isStale to label any retained value as degraded rather than presenting it as current without context. Identity transitions retire identity-owned data automatically.