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
| Status | Data | Meaning |
|---|---|---|
idle | Usually null | Skipped, anonymous required query, or cleared state |
pending | null or retained value | Current result is loading |
success | Query result | Current result settled successfully |
error | Usually null | Current execution failed |
pending is equivalent to status === 'pending' for normal queries.
Render all states
<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:
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.