Provide instant user feedback with optimistic updates and implement custom validation logic.
Mutations automatically invalidate cache and refetch data. However, you can add optimistic updates for instant feedback:
<script setup lang="ts">
const { items } = await useCollectionQuery('shopProducts')
const { update } = useCollectionMutation('shopProducts')
const toggleFeatured = async (product: Product) => {
// Optimistic: Update UI immediately
const index = items.value.findIndex(p => p.id === product.id)
if (index !== -1) {
items.value[index].featured = !items.value[index].featured
}
try {
// API call
await update(product.id, {
featured: !product.featured
})
// Cache auto-refetches on success
} catch (error) {
// Rollback on error
if (index !== -1) {
items.value[index].featured = !items.value[index].featured
}
}
}
</script>
Implement custom validation rules using Zod schemas with cross-field and async validation:
// composables/useProducts.ts
const schema = z.object({
name: z.string().min(1),
price: z.number().min(0),
discountPrice: z.number().optional()
})
.refine((data) => {
// Cross-field validation
if (data.discountPrice && data.discountPrice >= data.price) {
return false
}
return true
}, {
message: 'Discount must be less than price',
path: ['discountPrice']
})
.refine(async (data) => {
// Async validation
const exists = await $fetch(`/api/products/check-name?name=${data.name}`)
return !exists
}, {
message: 'Product name already exists'
})
Validate relationships between multiple fields:
.refine((data) => {
if (data.discountPrice && data.discountPrice >= data.price) {
return false
}
return true
}, {
message: 'Discount must be less than price',
path: ['discountPrice']
})
Perform asynchronous checks like database queries:
.refine(async (data) => {
const exists = await $fetch(`/api/products/check-name?name=${data.name}`)
return !exists
}, {
message: 'Product name already exists'
})
path to attach errors to specific fields