Mutations (Create, Update, Delete)
This example demonstrates how to use useMutation from @wisemen/vue-core-api-utils to handle data mutations like creating, updating, and deleting resources.
Create Mutation
typescript
// src/modules/contact/api/mutations/contactCreate.mutation.ts
import { useMutation } from '@wisemen/vue-core-api-utils'
import { ContactService } from '@/modules/contact/api/services/contact.service'
import type { ContactCreateForm } from '@/modules/contact/models/contact/create/contactCreateForm.model'
export function useContactCreateMutation() {
return useMutation({
queryFn: async (queryOptions: { body: ContactCreateForm }) => {
return await ContactService.create(queryOptions.body)
},
queryKeysToInvalidate: {
contactIndex: {},
},
})
}Update Mutation
typescript
// src/modules/contact/api/mutations/contactUpdate.mutation.ts
import { useMutation } from '@wisemen/vue-core-api-utils'
import { ContactService } from '@/modules/contact/api/services/contact.service'
import type { ContactUuid } from '@/modules/contact/models/contact/contactUuid.model'
import type { ContactUpdateForm } from '@/modules/contact/models/contact/update/contactUpdateForm.model'
interface Params {
contactUuid: ContactUuid
}
export function useContactUpdateMutation() {
return useMutation({
queryFn: async (queryOptions: {
body: ContactUpdateForm
params: Params
}) => {
return await ContactService.update(queryOptions.params.contactUuid, queryOptions.body)
},
queryKeysToInvalidate: {
contactDetail: {
contactUuid: (params) => params.contactUuid,
},
contactIndex: {},
},
})
}Delete Mutation
typescript
// src/modules/contact/api/mutations/contactDelete.mutation.ts
import { useMutation } from '@wisemen/vue-core-api-utils'
import { ContactService } from '@/modules/contact/api/services/contact.service'
import type { ContactUuid } from '@/modules/contact/models/contact/contactUuid.model'
interface Params {
contactUuid: ContactUuid
}
export function useContactDeleteMutation() {
return useMutation({
queryFn: async (queryOptions: { params: Params }) => {
return await ContactService.delete(queryOptions.params.contactUuid)
},
queryKeysToInvalidate: {
contactIndex: {},
},
})
}Usage in Vue Components
Create Mutation in Form
vue
<script setup lang="ts">
import { useForm } from 'formango'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useApiErrorToast } from '@/composables/api-error-toast/apiErrorToast.composable'
import { useContactCreateMutation } from '@/modules/contact/api/mutations/contactCreate.mutation'
import { contactCreateFormSchema } from '@/modules/contact/models/contact/create/contactCreateForm.model'
const router = useRouter()
const errorToast = useApiErrorToast()
const contactCreateMutation = useContactCreateMutation()
const form = useForm({
schema: contactCreateFormSchema,
onSubmit: async (values) => {
// Execute the mutation
const contactUuidResult = await contactCreateMutation.execute({
body: values,
})
// Handle the result using pattern matching
contactUuidResult.match(
(contactUuid) => {
// Success: navigate to the new contact
router.push({
name: 'contact-detail',
params: { contactUuid },
})
},
(error) => {
// Error: show error toast
errorToast.show(error)
},
)
},
})
</script>
<template>
<FormRoot :form="form">
<VcTextField
v-bind="toFormField(form.register('firstName'))"
label="First Name"
/>
<VcTextField
v-bind="toFormField(form.register('lastName'))"
label="Last Name"
/>
<FormSubmitButton>Create Contact</FormSubmitButton>
</FormRoot>
</template>Update Mutation in Form
vue
<script setup lang="ts">
import { useForm } from 'formango'
import { useRouter } from 'vue-router'
import { useApiErrorToast } from '@/composables/api-error-toast/apiErrorToast.composable'
import { useContactUpdateMutation } from '@/modules/contact/api/mutations/contactUpdate.mutation'
import type { ContactDetail } from '@/modules/contact/models/contact/detail/contactDetail.model'
import { ContactUpdateTransformer } from '@/modules/contact/models/contact/update/contactUpdate.transformer'
import { contactUpdateFormSchema } from '@/modules/contact/models/contact/update/contactUpdateForm.model'
const props = defineProps<{
contact: ContactDetail
}>()
const router = useRouter()
const errorToast = useApiErrorToast()
const contactUpdateMutation = useContactUpdateMutation()
const form = useForm({
initialState: ContactUpdateTransformer.toForm(props.contact),
schema: contactUpdateFormSchema,
onSubmit: async (values) => {
// Execute the mutation with both body and params
const response = await contactUpdateMutation.execute({
body: values,
params: {
contactUuid: props.contact.uuid,
},
})
// Handle the result - alternative approach using isErr()
if (response.isErr()) {
errorToast.show(response.error)
return
}
// Success: navigate back to detail view
await router.push({
name: 'contact-detail',
params: { contactUuid: props.contact.uuid },
})
},
})
</script>
<template>
<FormRoot :form="form">
<VcTextField
v-bind="toFormField(form.register('firstName'))"
label="First Name"
/>
<VcTextField
v-bind="toFormField(form.register('lastName'))"
label="Last Name"
/>
<FormSubmitButton>Save Changes</FormSubmitButton>
</FormRoot>
</template>Working with Mutation Results
Both approaches work with the ApiResult type from neverthrow:
Using `match()
typescript
const result = await mutation.execute({ body: data })
result.match(
(successData) => {
// Success case - type is inferred correctly
console.log('Created:', successData)
},
(error) => {
// Error case - error is properly typed
console.error('Failed:', error)
}
)Using isErr() / isOk()
typescript
const result = await mutation.execute({ body: data })
if (result.isErr()) {
console.error('Error:', result.error)
return
}
// TypeScript knows result is Ok here
console.log('Success:', result.value)Mutation Properties
The mutation hook returns:
execute: A function to execute the mutation with parametersisPending: ARef<boolean>indicating if the mutation is in progressisSuccess: ARef<boolean>indicating if the last mutation was successfulisError: ARef<boolean>indicating if the last mutation failedresult: ARef<ApiResult<T> | null>containing the last mutation result
Cache Invalidation Patterns
Simple Invalidation
Invalidates all queries with the matching key:
typescript
queryKeysToInvalidate: {
contactIndex: {},
}Targeted Invalidation
Invalidates specific queries based on parameters:
typescript
queryKeysToInvalidate: {
contactDetail: {
contactUuid: (params) => params.contactUuid,
},
}Multiple Keys
Invalidate multiple query caches at once:
typescript
queryKeysToInvalidate: {
contactDetail: {
contactUuid: (params) => params.contactUuid,
},
contactIndex: {},
userProfile: {},
}