Actions
How it works
Actions are an abstraction on top of Requests, which provide easy reactivity, type-safety, conventions and some additional features such as integrated loading states, metadata and pagination.
If you're calling your first-party API (or even third party, to some degree) you should use Actions.
INFO
While there are other Methods for calling Webservices, this one has the benefit of using "Vue-Style" Syntax with composables, aswell as the ability to treeshake very well.
Defining Actions
Actions themselves are composables which return an object of actions you define through the methods provided by useActions()
export const useCustomerActions = () => {
const { get, post, put, remove } = useActions()
return {
allCustomersAction: get<CustomerCollection, PaginationRequest>({
path: "/customer",
pagination: true,
}),
getCustomerAction: get<CustomerResource, GetByIdRequest>({
path: "/customer/{id}",
}),
createCustomerAction: post<CustomerResource, CustomerCreateRequest>({
path: "/customer",
}),
updateCustomerAction: put<CustomerResource, CustomerUpdateRequest>({
path: "/customer/{id}",
}),
removeCustomerAction: remove<CustomerResource>({
path: "/customer/{id}",
}),
}
}We have 4 main methods that useActions() provides, each for their respective HTTP verb.
| Method | HTTP Verb |
|---|---|
| get | GET |
| post | POST |
| put | PUT |
| remove | DELETE |
post, put and remove behave the same way, where get differs in usage.
Any Action can take up to 3 Type Parameters:
- The Response Type
- The Request Type (optional, defaults to
never) - The name of the key that gets returned (like
createfor post)
URL Paths and Data
You can put url variables into your path by using the {variable} syntax, like {id} above. If your Request contains an id key, it will be replaced with its value in the url.
so /customer/{id} becomes /customer/1 if you pass { id: 1 }
The remaining data fields will either get passed as FormData (post, update, remove) or as query string (get)
INFO
URL Replacement is especially helpful for Laravel's Implicit Route Binding feature, where you can get the correct Model via dependency injection.
Using Actions - GET
To use a get Action you should first import the given useActions composable and extract out the methods you need, e.g:
const { allCustomersAction } = useCustomerActions()allCustomersAction returns a composable on its own, which you then can destructure aswell:
const { get, resource: customers, loading } = allCustomersAction()INFO
You should use Aliasing to give the default resource key a more declarative name. You can use the same for the other keys if needed.
By this point, no endpoint has been called. To actually fire this get Request, you can call the get() method. If the Request was successful, resource will be populated with the data from the Backend.
try {
await get({
// Options specified in the Request, or none if type = never
})
// customers (resource) is a Ref populated with data by this point
} catch(e: unknown) {
// error handling
}Using Actions - POST/PUT/REMOVE
To use a post, put or remove Action you should first import the given useActions composable and extract out the methods you need, e.g:
const { createCustomerAction } = useCustomerActions()createCustomerAction returns a composable on its own, which you then can destructure aswell:
const { create, loading } = allCustomersAction()INFO
You can use Aliasing to give the create method a more declarative name, or if you're using multiple create actions in the same script.
By this point, no endpoint has been called. To actually fire this post, put or remove Request, you can call the create(), update() or remove() method. With mutable requests the result of the method call is the data returned.
try {
const customer = await create({
// Options specified in the Request, or none if type = never
})
// customer is now a Ref populated with the response data
} catch(e: unknown) {
// error handling
}Pagination
If a Request-Type implements the Toolkit provided PaginationRequest you enable further methods for easy pagination. Spire has built-in support for laravels default pagination.
Since runtime does not know anything about types, we also need to specify { pagination: true } in our request options.
export const useCustomerActions = () => {
const { get } = useActions()
return {
allCustomersAction: get<CustomerCollection, PaginationRequest>({
path: "/customer",
pagination: true,
}),
}
}This unlocks the previousPage, nextPage, firstPage and lastPage methods on this Action.
const { allCustomersAction } = useCustomerActions()
const { get, resource: customers, nextPage, previousPage } = allCustomersAction()
await get() // Page is 1 on first request (except if specified in request)
await nextPage() // Page is now 2, resource gets repopulatedINFO
The Action also returns a pagination composable to be used with other components, such as a custom pagination component. All the available Pagination methods and metadata is available within that composable.
previousPage(), nextPage(), firstPage(), lastPage(), currentPage, pages
Loading States
Each Action also provides a loading Ref that you can use for any loading states. You can combine this with your resource to make sure your components actually get the correct data, instead of undefined.
<script setup lang="ts">
const { allCustomersAction } = useCustomerActions()
const { get, resource: customers } = allCustomersAction()
onMounted(async () => {
await get()
})
</script>
<template>
<!-- customers will be of the correct type, since its only passed as a prop -->
<!-- when customers is not undefined -->
<CustomerList v-if="customers" :customers="customers" :loading="loading"/>
</template>