@volverjs/query-vue
is a Vue 3 library that provides a simple way to sync the state of your application with a data source. It is designed to be used with pinia
and @volverjs/data
repository.
# pnpm
pnpm add @volverjs/query-vue
# yarn
yarn add @volverjs/query-vue
# npm
npm install @volverjs/query-vue --save
Following examples are based on a RepositoryHttp
instance, but you can use any @volverjs/data
repository you want.
First of all, you need to initialize pinia
in your application, then you can create a store composable using defineStoreRepository()
:
import { HttpClient, RepositoryHttp } from '@volverjs/data'
// user-store.ts
import { defineStoreRepository } from '@volverjs/query-vue'
/* Define an User type */
export type User = {
id?: number
username: string
}
/* Create an HttpClient instance */
const httpClient = new HttpClient({
prefixUrl: 'https://my-domain.com'
})
/* Create a RepositoryHttp instance */
const usersRepository = new RepositoryHttp<User>(httpClient, 'users/:id?')
/* Create a store repository composable */
export const useUsersStore = defineStoreRepository(
usersRepository,
// the store name
'users'
)
In a component you can use the useUsersStore()
composable to get store actions and getters.
read()
action allow to read data from the repository:
<script setup lang="ts">
import { useUsersStore } from './user-store'
const { read } = useUsersStore()
/*
* With HttpRepository `read()` execute a
* GET request to https://my-domain.com/users
*/
const { data, isLoading, isError } = read()
</script>
<template>
<div v-if="isLoading">
Loading...
</div>
<div v-else-if="isError">
An error occurred! π
</div>
<div v-else>
<h1>Users</h1>
<ul>
<li v-for="user in data" :key="user.id">
{{ user.username }}
</li>
</ul>
</div>
</template>
read()
returns an object that contains:
const {
/* Reactive boolean that indicates if the request is loading */
isLoading,
/* Reactive boolean that indicates if the request has failed */
isError,
/* Reactive boolean that indicates if the request has succeeded */
isSuccess,
/* Reactive error object */
error,
/* Reactive status of the request */
status,
/* Reactive query object */
query,
/* Reactive array of data returned by the repository */
data,
/* Reactive metadata object returned by the repository */
metadata,
/* Reactive first item of the `data` array */
item,
/* Function to execute the `read()` action */
execute,
/* Function to stop `autoExecute` option */
stop,
/* Function to ignore reactive parameters updates */
ignoreUpdates,
/* Function to cleanup the store repository */
cleanup
} = read()
read()
accepts an optional parameters map.
const { data } = read({
page: 1
})
The parameters map can be reactive, data
will be automatically updated on parameters change with autoExecute
option:
const parameters = ref({
page: 1
})
const { data } = read(parameters, {
autoExecute: true
})
// ...
parameters.value = {
page: 2
}
// `read()` will be re-executed
autoExecute
can be stopped with stop()
function:
const parameters = ref({
page: 1
})
const { data, stop } = read(parameters, {
autoExecute: true
})
// ...
stop()
// `read()` will not be re-executed
A reactive parameters update can be ignored with ignoreUpdates
function:
const params = ref({
page: 1
})
const { data, ignoreUpdates } = read(params, {
autoExecute: true
})
// ...
ignoreUpdates(() => {
params.value = {
page: 1,
other: 'value'
}
})
You can re-execute the read()
action by calling the execute()
method with a parameters map that will override the current parameters:
const { data, execute } = read({
sort: 'name',
order: 'asc'
})
// ...
execute({
sort: 'name',
order: 'desc'
})
// `read()` will be re-executed with the given parameters
Persistance can be ignored with the force
option:
const { data, execute } = read({
sort: 'name',
order: 'asc'
})
// ...
execute(true)
// `read()` will be re-executed ignoring persistance
// ...
execute(
{
sort: 'name',
order: 'desc'
},
true
)
// `read()` will be re-executed with the given parameters ignoring persistance
A read()
can be executed later with immediate: false
option:
const { data, execute } = read(
{
page: 1
},
{
// `read()` will not be executed
immediate: false
}
)
// ...
execute()
// `read()` will be executed for the first time
A read()
can be executed when a condition is met with executeWhen
option:
const params = ref({
page: undefined
})
const { data, execute } = read(params, {
executeWhen: computed(() => params.value.page !== undefined)
})
// ...
params.value.page = 1
// `read()` will be executed
executeWhen
can also be function that receives the parameters and returns a boolean
:
const params = ref({
page: undefined
})
const { data, execute } = read(params, {
executeWhen: newParams => newParams.page !== undefined
})
// ...
params.value.page = 1
// `read()` will be executed
read()
accepts the following options:
const {
data
// ...
} = read(
/* The parameters map (default: undefined) */
params,
/* The options object (default: undefined) */
{
/*
* The name of the query (default: undefined)
* if not defined, the query name will be generated
*/
name: undefined,
/*
* Group all queries executed by the same read() action
* and exposes all items in `data` (default: false)
* Can be useful when you need to display a list of items
* (ex. inifinite scroll)
*/
group: false,
/*
* Store query results in a
* separate directory (default: false)
*/
directory: false,
/*
* Keep the query alive when
* the component is unmounted (default: false)
*/
keepAlive: false,
/*
* Execute the `read()` action immediately (default: true)
*/
immediate: true,
/*
* The query cache time in milliseconds (default: 60 * 60 * 1000)
*/
persistence: 60 * 60 * 1000,
/*
* A boolean reactive parameter (or a function) that indicates
* when the `read()` action should be executed (default: undefined)
* For example:
* `executeWhen: (newParams) => newParams.id !== undefined`
* Or:
* `executeWhen: computed(() => parameters.value.id !== undefined)`
*/
executeWhen: undefined,
/*
* Automatically execute the `read()` action
* on reactive parameters change (default: false)
*/
autoExecute: false,
/*
* The query auto execute throttle
* in milliseconds (default: 0)
*/
autoExecuteDebounce: 0,
/*
* Automatically execute the `read()` action
* on window focus (default: false)
*/
autoExecuteOnWindowFocus: false,
/*
* Automatically execute the `read()` action
* on document visibility change (default: false)
*/
autoExecuteOnDocumentVisibility: false,
}
)
submit()
action allow to create or update data from the repository. Generally, has the same behavior of read()
action but requires a payload
parameter.
<script setup lang="ts">
import { useUsersStore } from './user-store'
const { submit } = useUsersStore()
/*
* With HttpRepository `submit()` execute a
* POST request to https://my-domain.com/users
*/
const { isLoading, isError, isSuccess } = submit({
username: 'john.doe'
})
</script>
<template>
<div v-if="isLoading">
Loading...
</div>
<div v-else-if="isError">
An error occurred! π
</div>
<div v-else-if="isSuccess">
Submit success! π
</div>
</template>
submit()
returns an object that contains:
const {
/* Reactive boolean that indicates if the request is loading */
isLoading,
/* Reactive boolean that indicates if the request has failed */
isError,
/* Reactive boolean that indicates if the request has succeeded */
isSuccess,
/* Reactive error object */
error,
/* Reactive status of the request */
status,
/* Reactive query object */
query,
/* Reactive array of data returned by the repository */
data,
/* Reactive metadata object returned by the repository */
metadata,
/* Reactive first item of the `data` array */
item,
/* Function to execute the `submit()` action */
execute,
/* Function to stop `autoExecute` option */
stop,
/* Function to ignore reactive parameters updates */
ignoreUpdates,
/* Function to cleanup the store repository */
cleanup
} = submit()
submit()
accepts the following options:
const {
isSuccess
// ...
} = submit(
/* The submit payload (required) */
payload,
/* The parameters map (default: undefined) */
params,
/* The options object (default: undefined) */
{
/*
* The name of the query (default: undefined)
* if not defined, the query name will be generated
*/
name: undefined,
/*
* Keep the query alive when
* the component is unmounted (default: false)
*/
keepAlive: false,
/*
* Execute the `submit()` action immediately (default: true)
*/
immediate: true,
/*
* A boolean reactive parameter (or a function) that indicates
* when the `submit()` action should be executed (default: undefined)
* For example:
* `executeWhen: (newPayload, newParams) => newParams.id !== undefined`
* Or:
* `executeWhen: computed(() => parameters.value.id !== undefined)`
*/
executeWhen: undefined,
/*
* Automatically execute the `submit()` action
* on reactive parameters change (default: false)
*/
autoExecute: false,
/*
* The query auto execute throttle
* in milliseconds (default: 0)
*/
autoExecuteDebounce: 0,
/*
* Automatically execute the `submit()` action
* on window focus (default: false)
*/
autoExecuteOnWindowFocus: false,
/*
* Automatically execute the `submit()` action
* on document visibility change (default: false)
*/
autoExecuteOnDocumentVisibility: false,
}
)
As read()
also submit()
can be executed later too with immediate: false
option:
<script setup lang="ts">
import type { User } from './user-store'
import { ref } from 'vue'
import { useUsersStore } from './user-store'
const user = ref<User>({
username: ''
})
const { submit } = useUsersStore()
const { isLoading, isError, isSuccess, execute } = submit(user, undefined, {
// `submit()` will not be executed immediately
immediate: false
})
</script>
<template>
<div v-if="isLoading">
Loading...
</div>
<div v-else-if="isError">
An error occurred! π
</div>
<div v-else-if="isSuccess">
New user created! π
</div>
<div v-else>
<h1>Create User</h1>
<form @submit.prevent="execute()">
<input v-model="user.username" type="text" name="username" placeholder="Insert username">
<button type="submit">
Submit
</button>
</form>
</div>
</template>
remove()
action allow to delete data from the repository:
<script setup lang="ts">
import { useUsersStore } from './user-store'
const { remove } = useUsersStore()
/*
* With HttpRepository `remove()` execute a
* DELETE request to https://my-domain.com/users
*/
const { isLoading, isError, isSuccess } = remove({
// "id" or other "keyProperty" field
id: '123-321'
})
</script>
<template>
<div v-if="isLoading">
Loading...
</div>
<div v-else-if="isError">
An error occurred! π
</div>
<div v-else-if="isSuccess">
Delete success! π
</div>
</template>
remove()
returns an object that contains:
const {
/* Reactive boolean that indicates if the request is loading */
isLoading,
/* Reactive boolean that indicates if the request has failed */
isError,
/* Reactive boolean that indicates if the request has succeeded */
isSuccess,
/* Reactive error object */
error,
/* Reactive status of the request */
status,
/* Function to execute the `remove()` action */
execute,
} = remove({
// ...
})
remove()
accepts the following options:
const {
isSuccess
// ...
} = remove(
/* The parameters map (required) */
params,
/* The options object (default: undefined) */
{
/* Execute the `remove()` action immediately (default: true) */
immediate: true
}
)
@volverjs/query-vue
also exposes some useful components:
ReadProvider
is a component that allows to use read()
action in a component tree:
<script setup lang="ts">
import { useUsersStore } from './user-store'
const { ReadProvider } = useUsersStore()
</script>
<template>
<ReadProvider v-slot="{ isLoading, isError, data }">
<div v-if="isLoading">
Loading...
</div>
<div v-else-if="isError">
An error occurred! π
</div>
<div v-else>
<h1>Users</h1>
<ul>
<li v-for="user in data" :key="user.id">
{{ user.username }}
</li>
</ul>
</div>
</ReadProvider>
</template>
ReadProvider
exposes all the properties returned by read()
in the v-slot
scope and accepts the following props:
<template>
<ReadProvider
v-bind="{
/* The parameters map (default: undefined) */
params,
/* The `read()` options object (default: undefined) */
options,
}"
/>
</template>
SubmitProvider
is a component that allows to use submit()
action in a component tree:
<script setup lang="ts">
import type { User } from './user-store'
import { ref } from 'vue'
import { useUsersStore } from './user-store'
const { SubmitProvider } = useUsersStore()
const user = ref<User>({
username: ''
})
</script>
<template>
<SubmitProvider v-slot="{ isLoading, isError, isSuccess, execute }" v-model="user">
<div v-if="isLoading">
Loading...
</div>
<div v-else-if="isError">
An error occurred! π
</div>
<div v-else-if="isSuccess">
New user created! π
</div>
<div v-else>
<h1>Create user</h1>
<form @submit.prevent="execute()">
<input
v-model="user.username"
type="text"
name="username"
placeholder="Insert username"
>
<button type="submit">
Submit
</button>
</form>
</div>
</SubmitProvider>
</template>
SubmitProvider
exposes all the properties returned by submit()
in the v-slot
scope and accepts the following props:
<template>
<SubmitProvider
v-bind="{
/* The payload (required) */
modelValue,
/* The parameters map (default: undefined) */
params,
/* The `submit()` options object (default: { immediate: false }) */
options,
}"
/>
</template>
The v-model
directive provides a two-way binding so the reactive payload object will be updated when the submit()
action is executed (for example with the server response).
By default SubmitProvider
will not execute the submit()
action immediately, but you can change this behavior with immediate
option.
RemoveProvider
is a component that allows to use remove()
action in a component tree:
<script setup lang="ts">
import { useUsersStore } from './user-store'
const { RemoveProvider } = useUsersStore()
</script>
<template>
<RemoveProvider
v-slot="{ isLoading, isError, isSuccess, execute }"
:params="{ id: '123-321' }"
>
<div v-if="isLoading">
Loading...
</div>
<div v-else-if="isError">
An error occurred! π
</div>
<div v-else-if="isSuccess">
Delete success! π
</div>
<div v-else>
<button type="button" @click="execute()">
Delete
</button>
</div>
</RemoveProvider>
</template>
RemoveProvider
exposes all the properties returned by remove()
in the v-slot
scope and accepts the following props:
<template>
<RemoveProvider
v-bind="{
/* The parameters map (required) */
params,
/* The `remove()` options object (default: { immediate: false }) */
options,
}"
/>
</template>
By default RemoveProvider
will not execute the remove()
action immediately, but you can change this behavior with immediate
option.
@volverjs/query-vue
is inspired by React Query
.