Add a Mutation
Write Convex data and observe the live query update without a manual refetch.
Add a mutation to the todo app from the previous page.
Define the backend write
import { mutation, query } from './_generated/server'
import { ConvexError, v } from 'convex/values'
export const list = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query('todos').withIndex('by_created').order('desc').take(50)
},
})
export const create = mutation({
args: {
text: v.string(),
},
handler: async (ctx, args) => {
const text = args.text.trim()
if (!text || text.length > 200) {
throw new ConvexError({
code: 'INVALID_TODO_TEXT',
message: 'Todo text must contain 1 to 200 characters',
})
}
return await ctx.db.insert('todos', {
text,
completed: false,
createdAt: Date.now(),
})
},
})Connect the form
Replace the page with:
<script setup lang="ts">
import { api } from '#convex/api'
const text = ref('')
const { data: todos, status, error } = useConvexQuery(api.todos.list)
const createTodo = useConvexMutation(api.todos.create)
async function submit() {
const value = text.value.trim()
if (!value) return
try {
await createTodo({ text: value })
text.value = ''
} catch {
// createTodo.error contains the normalized public error for this attempt.
}
}
</script>
<template>
<main>
<h1>Todos</h1>
<form @submit.prevent="submit">
<input v-model="text" maxlength="200" aria-label="Todo text" />
<button :disabled="createTodo.pending || !text.trim()">
{{ createTodo.pending ? 'Adding…' : 'Add todo' }}
</button>
</form>
<p v-if="createTodo.error">{{ createTodo.error.message }}</p>
<p v-if="status === 'pending'">Loading todos…</p>
<p v-else-if="error">Could not load todos.</p>
<ul v-else>
<li v-for="todo in todos ?? []" :key="todo._id">
{{ todo.text }}
</li>
</ul>
</main>
</template>What updates the list
The mutation writes to todos. Convex recomputes subscribed queries whose dependencies changed and publishes the new todos.list result.
The module updates the query ref. The page does not call refresh() after the mutation.
createTodo(args) resolves with the generated mutation result and rejects with
ConvexCallError on failure. Its readonly refs describe the newest invocation.
Next, either explore query guides or add authentication.