Queries fetch data from Convex. They automatically subscribe to changes and update in real-time.
<script setup lang="ts">
import { api } from '~/convex/_generated/api'
// Fetch todos with real-time updates
const { data: todos, status, error } = useConvexQuery(api.todos.list, {})
</script>
<template>
<div class="todos">
<!-- Loading state -->
<div v-if="status === 'pending'">Loading todos...</div>
<!-- Error state -->
<div v-else-if="status === 'error'" class="error">Failed to load: {{ error?.message }}</div>
<!-- Empty state -->
<div v-else-if="status === 'success' && !todos?.length">No todos yet. Add one below!</div>
<!-- Data -->
<ul v-else>
<li v-for="todo in todos" :key="todo._id">
{{ todo.text }}
</li>
</ul>
</div>
</template>
What's happening:
useConvexQuery fetches data during SSR for fast initial loadMutations modify data on the server. They return a promise and track loading/error state.
<script setup lang="ts">
import { api } from '~/convex/_generated/api'
// Query for listing
const { data: todos, status } = useConvexQuery(api.todos.list, {})
// Mutation for creating
const { mutate: createTodo, pending, error: createError } = useConvexMutation(api.todos.create)
// Form state
const newTodoText = ref('')
async function handleSubmit() {
if (!newTodoText.value.trim()) return
try {
await createTodo({ text: newTodoText.value })
newTodoText.value = '' // Clear input on success
} catch {
// Error is automatically captured in createError
}
}
</script>
<template>
<div class="todos">
<!-- Create form -->
<form @submit.prevent="handleSubmit">
<input v-model="newTodoText" placeholder="What needs to be done?" :disabled="pending" />
<button type="submit" :disabled="pending || !newTodoText.trim()">
{{ pending ? 'Adding...' : 'Add Todo' }}
</button>
</form>
<p v-if="createError" class="error">
{{ createError.message }}
</p>
<!-- Todo list -->
<ul v-if="status === 'success'">
<li v-for="todo in todos" :key="todo._id">
{{ todo.text }}
</li>
</ul>
</div>
</template>
What's happening:
useConvexMutation returns a mutate function and tracks pending/error statemutate(), it sends the data to ConvexLet's add toggle and delete functionality:
<script setup lang="ts">
import { api } from '~/convex/_generated/api'
const { data: todos, status } = useConvexQuery(api.todos.list, {})
const { mutate: createTodo, pending: isCreating } = useConvexMutation(api.todos.create)
const { mutate: toggleTodo } = useConvexMutation(api.todos.toggle)
const { mutate: deleteTodo } = useConvexMutation(api.todos.remove)
const newTodoText = ref('')
async function handleSubmit() {
if (!newTodoText.value.trim()) return
await createTodo({ text: newTodoText.value })
newTodoText.value = ''
}
</script>
<template>
<div class="todos">
<form @submit.prevent="handleSubmit">
<input v-model="newTodoText" :disabled="isCreating" />
<button :disabled="isCreating">Add</button>
</form>
<ul v-if="status === 'success'">
<li v-for="todo in todos" :key="todo._id">
<input type="checkbox" :checked="todo.completed" @change="toggleTodo({ id: todo._id })" />
<span :class="{ completed: todo.completed }">
{{ todo.text }}
</span>
<button @click="deleteTodo({ id: todo._id })">Delete</button>
</li>
</ul>
</div>
</template>
<style scoped>
.completed {
text-decoration: line-through;
opacity: 0.6;
}
</style>
For reference, here's what the Convex functions look like:
import { v } from 'convex/values'
import { query, mutation } from './_generated/server'
export const list = query({
handler: async (ctx) => {
return await ctx.db.query('todos').collect()
},
})
export const create = mutation({
args: { text: v.string() },
handler: async (ctx, args) => {
return await ctx.db.insert('todos', {
text: args.text,
completed: false,
})
},
})
export const toggle = mutation({
args: { id: v.id('todos') },
handler: async (ctx, args) => {
const todo = await ctx.db.get(args.id)
if (!todo) throw new Error('Todo not found')
await ctx.db.patch(args.id, { completed: !todo.completed })
},
})
export const remove = mutation({
args: { id: v.id('todos') },
handler: async (ctx, args) => {
await ctx.db.delete(args.id)
},
})
| Concept | Description |
|---|---|
| Queries | Read data, automatically subscribe to changes |
| Mutations | Write data, return promises |
| Status | 'pending' | 'success' | 'error' | 'idle' |
| Real-time | Changes sync automatically across all clients |
| SSR | Queries run on server for fast initial load |