@wisemen/vue-core-api-utils
Version:
121 lines (95 loc) • 3.42 kB
Markdown
---
name: writing-mutations
description: >
Create, update, delete resources using useMutation, typed queryKeysToInvalidate, AsyncResult error handling, execute function, request shape with body/params separation.
type: core
library: vue-core-api-utils
---
# @wisemen/vue-core-api-utils — Writing Mutations
Create, update, and delete resources. Mutations automatically invalidate affected queries and return AsyncResult for explicit error handling.
## Setup
```typescript
import { useMutation } from '@/api'
import { ContactService } from '@/services'
export function useCreateContact() {
return useMutation({
queryFn: async ({ body }: { body: ContactCreateForm }) => {
return await ContactService.create(body)
},
queryKeysToInvalidate: {
contactList: {}, // Invalidate all contactList queries
},
})
}
```
Every mutation must list which queries to invalidate via `queryKeysToInvalidate`.
## Core Patterns
### Execute a mutation and handle the result
```typescript
import { useCreateContact } from '@/composables'
const { execute, result } = useCreateContact()
async function handleSubmit(formData: ContactCreateForm) {
const response = await execute({ body: formData })
if (response.isOk()) {
console.log('Created contact:', response.getValue())
// Invalidated queries will refetch automatically
} else if (response.isErr()) {
const error = response.getError()
if ('errors' in error && error.errors[0].code === 'EMAIL_EXISTS') {
toast.error('That email is already registered')
} else {
toast.error('Creation failed')
}
}
}
```
Always `await execute()` and check the result state before continuing.
### Update mutation with specific query invalidation
```typescript
export function useUpdateContact(contactUuid: string) {
return useMutation({
queryFn: async ({ body }: { body: ContactUpdateForm }) => {
return await ContactService.update(contactUuid, body)
},
queryKeysToInvalidate: {
contactDetail: {
contactUuid: (_params, _result) => contactUuid, // Invalidate only this contact's detail query
},
contactList: {}, // Invalidate all contactList queries
},
})
}
```
You can invalidate multiple queries. Include queries that depend on the data you're changing.
### Form integration
```vue
<script setup lang="ts">
import { ref } from 'vue'
import { useCreateContact } from '@/composables'
const form = reactive({ name: '', email: '' })
const { execute, result } = useCreateContact()
async function handleSubmit() {
const response = await execute({ body: form })
if (response.isOk()) {
router.push('/contacts')
}
// If isErr, form stays visible for user to retry
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.name" />
<input v-model="form.email" />
<button :disabled="result.isLoading()">
{{ result.isLoading() ? 'Creating...' : 'Create' }}
</button>
<div v-if="result.isErr() && 'errors' in result.getError()">
Error: {{ result.getError().errors[0].detail }}
</div>
</form>
</template>
```
Use `result.isLoading()` to disable the button during mutation.
## See Also
- [Cache Management](../cache-management/SKILL.md) — Understanding which queries to invalidate
- [Writing Queries](../writing-queries/SKILL.md) — Mutations invalidate queries; understand queries first