Advanced
Optimistic Updates & Custom Validation
Enhance UX with instant feedback and custom validation rules
Provide instant user feedback with optimistic updates and implement custom validation logic.
Optimistic Updates
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>
How Optimistic Updates Work
- Immediate Update: Modify local state before the API call
- API Call: Make the actual mutation request
- Auto-Refetch: On success, cache automatically refreshes
- Rollback: On error, revert the local state change
Custom Validation
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'
})
Validation Types
Cross-Field Validation
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']
})
Async Validation
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'
})
Best Practices
Optimistic Updates
- Always implement error rollback
- Use for frequent, low-risk operations (toggles, favorites)
- Avoid for critical operations (payments, deletions)
- Provide visual feedback during the operation
Custom Validation
- Keep validation logic close to your forms
- Use clear, user-friendly error messages
- Specify the
pathto attach errors to specific fields - Debounce async validation to reduce API calls
- Cache async validation results when possible
Related Sections
- Data Operations - CRUD operations
- Caching - Cache invalidation
- Form Patterns - Form best practices