# Feathers-Vuex Composition API 3.0.0+
In addition to the Renderless Components and the Mixins, Feathers-Vuex includes utilities that let you take advantage of the Vue Composition API (opens new window).
It's important to note that the @vue/composition-api
plugin must be registered BEFORE you import App.vue
. This means that you need to modify your main.js
file to look like this:
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
// Register the Composition API plugin BEFORE you import App.vue
Vue.use(VueCompositionApi)
import App from './App.vue'
import router from './router'
import store from './store/store'
import './plugins/plugins'
Vue.config.productionTip = true
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
If you forget to register the plugin, first, you will see a console warning from Vue stating `[Vue warn]: Error in data(): "Error: [vue-composition-api] must call Vue.use(plugin) before using any function.". To fix this, make sure to register the plugin BEFORE you call any Composition utility functions, as shown above.
# Setup
Before you can use the useFind
and useGet
composition functions, you'll need to install the Vue Composition API (opens new window) plugin.
# Detour: Reading a TypeScript interface
The next few sections show various TypeScript interfaces, which are basically shorthand descriptions of the types of data that make up a variable. In this case, they're used to show the options
object which can be passed to each of the composition api utilities. If this is your first time with interfaces, here's a quick primer as an alternative to reading the TypeScript interface docs (opens new window):
- In the first interface example, below,
UseFindOptions
is the name of the interface, similar to naming any other variable. When using TypeScript, you can import and pass interfaces around like variables. - Each line of the interface describes a property.
- The part before the
:
is the name of the property. - The part after the
:
describes what type of variable it can be. - You can look at any
|
after the:
as a conditional "or" - Any property followed by a
?
is optional. - Any property not followed by a
?
is required.
# useFind 3.0.0+
The useFind
utility reduces boilerplate for querying with fall-through cache and realtime updates. To get started with it you provide a model
class and a computed params
object.
Let's use the example of creating a User Guide, where we need to pull in the various Tutorial
records from our tutorials
service. We'll keep it simple in the template and just show a list of the names.
<template>
<div>
<li v-for="tutorial in tutorials" :key="tutorial._id">
{{ tutorial.name }}
</li>
</div>
</template>
<script>
import { computed } from '@vue/composition-api'
import { useFind } from 'feathers-vuex'
export default {
name: 'UserGuide',
setup(props, context) {
// 1. Get a reference to the model class
const { Tutorial } = context.root.$FeathersVuex.api
// 2. Create a computed property for the params
const tutorialsParams = computed(() => {
return {
query: {}
}
})
// 3. Provide the model and params in the options
const tutorialsData = useFind({ model: Tutorial, params: tutorialsParams })
// 4. Return the data, named as you prefer
return {
tutorials: tutorialsData.items
}
}
}
</script>
Let's review each of the numbered comments, above:
- Get a reference to the model class. With the Vue Composition API, there's no
this
object. It has been replaced by the context object. So, only when using the composition API, the$FeathersVuex
object is found atcontext.root.$FeathersVuex
- Create a computed property for the params. Return an object with a nested
query
object.
# Options
Since we learned earlier how to read a TypeScript interface, let's look at the TypeScript definition for the UseFindOptions
interface.
interface UseFindOptions {
model: Function
params: Params | Ref<Params>
fetchParams?: Params | Ref<Params>
queryWhen?: Ref<Function>
qid?: string
immediate?: boolean
}
And here's a look at each individual property:
model
must be a Feathers-Vuex Model class. The Model'sfind
andfindInStore
methods are used to query data.params
is a FeathersJS Params object OR a Composition APIref
(orcomputed
, since they return aref
instance) which returns a Params object.- When provided alone (without the optional
fetchParams
), this same query is used for both the local data store and the API requests. - Explicitly returning
null
will prevent an API request from being made. - You can use
params.qid
to dynamically specify the query identifier for any API request. Theqid
is used for tracking pagination data and enabling the fall-through cache across multiple queries. - Set
params.paginate
totrue
to turn off realtime updates for the results and defer pagination to the API server. Pagination works the same as for the makeFindMixin pagination. - Set
params.debounce
to an integer and the API requests will automatically be debounced by that many milliseconds. For example, settingdebounce: 1000
will assure that the API request will be made at most every 1 second. - Set
params.temps
totrue
to include temporary (local-only) items in the results. Temporary records are instances that have been created but not yet saved to the database. - Set
params.copies
totrue
to include cloned items in the results. The queried items get replaced with the corresponding copies fromcopiesById
- When provided alone (without the optional
fetchParams
This is a separate set of params that, when provided, will become the params sent to the API server. Theparams
will then only be used to query data from the local data store.- Explicitly returning
null
will prevent an API request from being made.
- Explicitly returning
queryWhen
must be acomputed
property which returns aboolean
. It provides a logical separation for preventing API requests outside of theparams
.qid
allows you to specify a query identifier (used in the pagination data in the store). This can also be set dynamically by returning aqid
in the params.immediate
, which istrue
by default, determines if the internalwatch
should fire immediately. Setimmediate: false
and the query will not fire immediately. It will only fire on subsequent changes to the params.
# Returned Attributes
Notice the tutorialsData
in the previous example. You can see that there's an tutorialsData.items
property, which is returned at the bottom of the setup
method as tutorials
. There are many more attributes available in the object returned from useFind
. We can learn more about the return values by looking at its TypeScript interface, below.
interface UseFindData {
items: Ref<any>
paginationData: Ref<object>
servicePath: Ref<string>
qid: Ref<string>
isPending: Ref<boolean>
haveBeenRequested: Ref<boolean>
haveLoaded: Ref<boolean>
error: Ref<Error>
debounceTime: Ref<number>
latestQuery: Ref<object>
isLocal: Ref<boolean>
find: Function
}
Let's look at the functionality that each one provides:
items
is the list of results. By default, this list will be reactive, so if new items are created which match the query, they will show up in this list automagically.servicePath
is the FeathersJS service path that is used by the current model. This is mostly only useful for debugging.isPending
is a boolean that indicates if there is an active query. It is set totrue
just before each outgoing request. It is set tofalse
after the response returns. Bind to it in the UI to show an activity indicator to the user.haveBeenRequested
is a boolean that is set totrue
immediately before the first query is sent out. It remains true throughout the life of the component. This comes in handy for first-load scenarios in the UI.haveLoaded
is a boolean that is set to true after the first API response. It remainstrue
for the life of the component. This also comes in handy for first-load scenarios in the UI.isLocal
is a boolean that is set to true if this data is local only.qid
is currently the primaryqid
provided in params. It might become more useful in the future.debounceTime
is the current number of milliseconds used as the debounce interval.latestQuery
is an object that holds the latest query information. It populates after each successful API response. The information it contains can be used to pull data from thepaginationData
.paginationData
is an object containing all of the pagination data for the current service.error
is null until an API error occurs. The error object will be serialized into a plain object and available here.find
is the find method used internally. You can manually make API requests. This is most useful for when you havepaginate: true
in the params. You can manually query refreshed data from the server, when desired.
# Working with Refs
Pay special attention to the properties of type Ref
, in the TypeScript interface, above. Those properties are Vue Composition API ref
instances. This means that you need to reference their value by using .value
. In the next example the completeTodos
and incompleteTodos
are derived from the todos
, using todos.value
<template>
<div>
<li v-for="tutorial in tutorials" :key="tutorial._id">
{{ tutorial.name }}
</li>
</div>
</template>
<script>
import { computed } from '@vue/composition-api'
import { useFind } from 'feathers-vuex'
export default {
name: 'UserGuide',
setup(props, context) {
const { Todo } = context.root.$FeathersVuex.api
const todosParams = computed(() => {
return {
query: {}
}
})
const { items: todos } = useFind({ model: Todo, params: todosParams })
// Notice the "todos.value"
const completeTodos = computed(() => todos.value.filter(todo => todo.isComplete))
const incompleteTodos = computed(() => todos.value.filter(todo => !todo.isComplete))
return {
todos,
completeTodos,
incompleteTodos
}
}
}
</script>
# Comparison to makeFindMixin
If you have already used the makeFindMixin
, the useFind
composition function will be very familiar, since it offers the same functionality in a more powerful way. There are a few differences, though.
useFind
is more TypeScript friendly. Since the mixins depended on adding dynamic attribute names that wouldn't overlap, TypeScript tooling and autocomplete weren't very effective. The attributes returned fromuseFind
are always consistent.- Instead of providing a service name, you provide a service Model from the
$FeathersVuex
Vue plugin. - The default behavior of
useFind
is to immediately query the API server. ThemakeFindMixin
, by default, would wait until the watcher noticed the change. This is to match the default behavior ofwatch
in the Vue Composition API. You can pass{ immediate: false }
in theuseFind
options, which will be passed directly to the internalwatch
on the params.
Note that with the Vue Options API (aka the only way to write components in Vue 2.0) the models are found in this.$FeathersVuex
. With the Vue Composition API, this object is now at context.root.$FeathersVuex
.
# useGet 3.0.0+
The useGet
Composition API utility provides the same fall-through cache functionality as useFind
. It has a slightly simpler API, only requiring a model
and id
instead of the params
object. Still, the params
object can be used to send along additional query parameters in the request. Below is an example of how you might use the useGet
utility.
<template>
<div>
<div v-if="post">{{ post.body }}</div>
<div v-else-if="isPending">Loading</div>
<div v-else>Post not found.</div>
</div>
</template>
<script>
import { computed } from '@vue/composition-api'
import { useFind, useGet } from 'feathers-vuex'
export default {
name: 'BlogPostView',
props: {
id: {
type: String,
required: true
}
},
setup(props, context) {
const { Post } = context.root.$FeathersVuex.api
// Get the patient record
const { item: post, isPending } = useGet({
model: Post,
id: props.id
})
return {
post,
isPending
}
}
}
<script>
See the Routing with useGet portion of the patterns section, below, to see how to hook up the above component to vue-router.
# Options
We learned earlier how to read a TypeScript interface, so let's look at the TypeScript interface for the UseGetOptions
.
interface UseGetOptions {
model: Function
id: null | string | number | Ref<null> | Ref<string> | Ref<number>
params?: Params | Ref<Params>
queryWhen?: Ref<Function>
local?: boolean
immediate?: boolean
}
And here's a look at each individual property:
model
must be a Feathers-Vuex Model class. The Model'sget
andgetFromStore
methods are used to query data.id
must be a record's unique identifier (id
or_id
, usually) or a ref or computed property which returns one.- When the
id
changes, the API will be queried for the new record (unlessqueryWhen
evaluates tofalse
). - If the
id
isnull
, no query will be made.
- When the
params
is a FeathersJS Params object OR a Composition APIref
(orcomputed
, since they return aref
instance) which returns a Params object.- Unlike the
useFind
utility,useGet
does not currently have built-in debouncing.
- Unlike the
queryWhen
must be acomputed
property which returns aboolean
. It provides a logical separation for preventing API requests apart fromnull
in theid
.immediate
, which istrue
by default, determines if the internalwatch
should fire immediately. Setimmediate: false
and the query will not fire immediately. It will only fire on subsequent changes to theid
orparams
.
# Returned Attributes
interface UseGetData {
item: Ref<any>
servicePath: Ref<string>
isPending: Ref<boolean>
hasBeenRequested: Ref<boolean>
hasLoaded: Ref<boolean>
isLocal: Ref<boolean>
error: Ref<Error>
get: Function
}
# FeathersVuexPagination
As of version 3.8.0
, Feathers-Vuex includes the FeathersVuexPagination
renderless component. This component pairs with the useFind
utility to simplify handling server-side pagination. If you use the FeathersVuex Vue plugin, the FeathersVuexPagination
component is registered as a global component. Since it's a renderless component, you'll need to supply your own UI to the default slot.
# Usage Steps
The steps to using the FeathersVuexPagination
component include
- Create a
pagination
ref containing an object with$limit
and$skip
properties. - Create a
params
computed property. Be sure to include aquery
object andpaginate: true
in the params. It's also recommended that you use aqid
to identify the component that you're using, otherwise pagination data could mix with other parts of the UI and create an inconsistent experience. - Merge the
pagination
into theparams.query
. - Pass the
params
object to theuseFind
utility. - Pull the
items
array andlatestQuery
object fromuseFind
's return value. - Make the
items
,latestQuery
, andpagination
available to the component's template by returning them in the setup method. - Provide the above two variables to the
FeathersVuexPagination
component as props. - Provide a UI inside the default slot, binding to variables and desired events.
# Props
The FeathersVuexPagination
component only accepts two props.
v-model
(orvalue
): Receives thepagination
object, which must contain the$limit
and$skip
properties.latest-query
, which receives thelatestQuery
object returned by theuseFind
utility.
# Default Slot Scope
The following variables and functions are provided by the default slot:
currentPage
{Number} The current page number, based on the current$limit
and$skip
values provided to thev-model
.pageCount
{Number} If the response from the API server includes atotal
attribute, thepageCount
will be the total number of pages, based on the current value of$limit
.canPrev
{Boolean} Will be true if not on the first page (can go to a previous page). This value is useful for enabling/disabling a button in the UI, if you desire to give that kind of feedback to the user.canNext
{Boolean} Will be true if not on the last page (can go to a next page). This value is useful for enabling/disabling a button in the UI, if you desire to give that kind of feedback to the user.toStart
{Function} When called, will move to the first page.toEnd
{Function} When called, will move to the last page.toPage(n: number)
{Function} When called with a page number as its first argument, will move to that page number. If the page number is greater than the total number of pages, will go to the last page. If the page number is less than 0, will go to the first page.prev
{Function} When called, moves to the previous page. Will not go below page 1.next
{Function} When called, moves tot the next page. Will not go past the last page.
# Example
Here is an example of how to use it. It assumes that you have a /listings
service with a Listing
model. The next code example below this one shows the PaginationUi
component. Note that not all slot scope variables and functions are required. To implement basic pagination, only the currentPage
, pageCount
, next
, and prev
values are really necessary. The other slot scope values allow providing a customized pagination experience.
<template>
<div>
<!-- 7. ^ -->
<FeathersVuexPagination v-model="pagination" :latest-query="latestQuery">
<!-- 8. ^ -->
<template #default="{ currentPage, pageCount, toStart, toEnd, toPage, next, prev, canNext, canPrev }">
<PaginationUi
:current-page="currentPage"
:page-count="pageCount"
:can-prev="canPrev"
:can-next="canNext"
@to-start="toStart"
@to-end="toEnd"
@to-page="toPage"
@next="next"
@prev="prev"
/>
</template>
</FeathersVuexPagination>
<!-- Results -->
<div>
<div v-for="item in items" :key="item._id">
{{ item }}
</div>
</div>
</div>
</template>
<script>
import { ref, computed, watch } from '@vue/composition-api'
import { models, useFind, FeathersVuexPagination } from 'feathers-vuex'
import PaginationUi from './PaginationUi.vue'
export default {
name: 'PaginationExample',
components: {
FeathersVuexPagination,
PaginationUi
},
setup(props, context) {
const { Listing } = models.api
// 1.^
const pagination = ref({
$limit: 20,
$skip: 0
})
// 2.^
const params = computed(() => {
const query = {}
// 3.^
Object.assign(query, pagination.value)
return { query, qid: 'listingsPage', paginate: true }
})
// 4.^
const data = useFind({ model: Listing, params: params })
// 5.^ (can be merged into previous line)
const { items, latestQuery } = data
// 6.^
return { items, pagination, latestQuery }
}
}
</script>
As promised, here is the example code for the PaginationUi
component used above. It includes some TailwindCSS utility classes in the markup in order to show how one might use the canPrev
and canNext
properties. Keep in mind that this is just an example. You can provide whatever experience you want for your users by creating your own component.
<template>
<div class="flex flex-row items-center mt-2">
<button
type="button"
class="rounded py-0.5 flex flex-row items-center justify-center w-24 mr-1"
:class="[
canPrev
? 'bg-gray-600 text-white'
: 'bg-gray-400 text-gray-500 cursor-not-allowed'
]"
:disabled="!canPrev"
@click="e => $emit('prev', e)"
>
<ChevronLeftIcon />
<p class="pr-2">Previous</p>
</button>
<button
type="button"
class="bg-gray-600 text-white rounded py-0.5 flex flex-row items-center justify-center w-24 mr-1"
@click="e => $emit('to-start', e)"
>
<p class="pr-2">To Start</p>
</button>
<div class="flex flex-grow justify-center items-center">
Page
<input
:value="currentPage"
type="number"
:min="1"
:max="pageCount"
class="w-12 rounded py-0.5 mx-1.5 dark:bg-gray-900 text-center"
@input="event => $emit('to-page', event.target.value)"
/>
of {{ pageCount }}
</div>
<button
type="button"
class="bg-gray-600 text-white rounded py-0.5 flex flex-row items-center justify-center w-24 mr-1"
@click="e => $emit('to-end', e)"
>
<p class="pr-2">To End</p>
</button>
<button
type="button"
class="rounded py-0.5 flex flex-row items-center justify-center w-24"
:class="[
canNext
? 'bg-gray-600 text-white'
: 'bg-gray-400 text-gray-500 cursor-not-allowed'
]"
:disabled="!canNext"
@click="e => $emit('next', e)"
>
<div class="w-12">Next</div>
<ChevronRightIcon />
</button>
</div>
</template>
<script>
import { ChevronLeftIcon, ChevronRightIcon } from 'vue-feather-icons'
export default {
name: 'SidebarPagination',
components: {
ChevronLeftIcon,
ChevronRightIcon
},
props: {
currentPage: {
type: Number,
required: true
},
pageCount: {
type: Number,
required: true
},
canPrev: {
type: Boolean,
required: true
},
canNext: {
type: Boolean,
required: true
}
}
}
</script>
# Patterns & Examples
# Server-Side Pagination
Similar to what was introduced with the makeFindMixin
in Feathers-Vuex 2.0, the useFind
API supports server-side pagination. It is enabled by passing paginate: true
in the params
(or the fetchParams
if you're using separate queries). For an overview of how it works, refer to the makeFindMixin pagination docs.
# Simultaneous Queries
Let's look at an example where we have two separate tables and we want live-queried lists for both of them. This example will show a component for a doctor's office that pulls up a patient by id
using useGet
then retrieves all of the patient's appointments
using useFind
.
<template>
<div>
<div>{{ patient.name }}</div>
<li v-for="appointment in appointments" :key="appointment._id">
{{ appointment.date }}
</li>
</div>
</template>
<script>
import { computed } from '@vue/composition-api'
import { useFind, useGet } from 'feathers-vuex'
export default {
name: 'PatientAppointments',
props: {
id: {
type: String,
required: true
}
},
setup(props, context) {
const { Patient, Appointment } = context.root.$FeathersVuex.api
// Get the patient record
const { item: patient } = useGet({ model: Patient, id: props.id })
// Get all of the appointments belonging to the current patient
const appointmentsParams = computed(() => {
return {
query: {
userId: props.id,
$sort: { date: -1 }
}
}
})
const { items: appointments } = useFind({
model: Appointment,
params: appointmentsParams
})
return {
patient,
appointments
}
}
}
</script>
# Deferring Queries
In the previous example, the requests for the patient
and appointments
are made at the same time because the user's id
is available, already. What if we were required to load appointments
after the patient
record finished loading? We could change the appointmentsParams
to return null
until the patient
record becomes available, as shown in the following example:
<template>
<div>
<div>{{ patient.name }}</div>
<li v-for="appointment in appointments" :key="appointment._id">
{{ appointment.date }}
</li>
<div v-if="!appointments.length && haveLoaded">
No appointments have been scheduled for this patient.
</div>
</div>
</template>
<script>
import { computed } from '@vue/composition-api'
import { useFind, useGet } from 'feathers-vuex'
export default {
name: 'PatientAppointments',
props: {
id: {
type: String,
required: true
}
},
setup(props, context) {
const { Patient, Appointment } = context.root.$FeathersVuex.api
// Get the patient record
const { item: patient } = useGet({ model: Patient, id: props.id })
// Get all of the appointments belonging to the current patient
const appointmentsParams = computed(() => {
// (1)
if (!patient.value) {
return null
}
// (2)
return {
query: {
userId: patient.value._id,
$sort: { date: -1 }
}
}
})
const { items: appointments, haveLoaded } = useFind({
model: Appointment,
params: appointmentsParams
})
return {
patient,
appointments,
haveLoaded
}
}
}
</script>
Reviewing the above snippet, while there is no patient
record, the appointmentsParams
computed property returns null
at comment (1)
. This will prevent any query from going out to the API server.
Once the patient
has loaded, the full params object is returned at comment (2)
. This allows the useFind
utility to make the request.
# Showing Loading State
This next example builds on the previous one and adds loading state for both the patient
and the appointments
.
<template>
<div>
<div v-if="isPatientLoading">Loading</div>
<div v-else>{{ patient.name }}</div>
<li v-for="appointment in appointments" :key="appointment._id">
{{ appointment.date }}
</li>
<div v-if="!appointments.length && haveLoaded">
No appointments have been scheduled for this patient.
</div>
</div>
</template>
<script>
import { computed } from '@vue/composition-api'
import { useFind, useGet } from 'feathers-vuex'
export default {
name: 'PatientAppointments',
props: {
id: {
type: String,
required: true
}
},
setup(props, context) {
const { Patient, Appointment } = context.root.$FeathersVuex.api
const {
item: patient,
isPending: isPatientLoading
} = useGet({ model: Patient, id: props.id })
const appointmentsParams = computed(() => {
if (!patient.value) {
return null
}
return {
query: {
userId: patient.value._id,
$sort: { date: -1 }
}
}
})
const { items: appointments, haveLoaded } = useFind({
model: Appointment,
params: appointmentsParams
})
return {
patient,
isPatientLoading,
appointments,
haveLoaded
}
}
}
</script>
# Using queryWhen
The queryWhen
option for both useFind
and useGet
comes in handy when you want to conditional prevent API queries. One use case for this is to prevent extra queries by checking if an item already exists in the vuex store. This next example shows how to stop the get
request if you already have a patient record with the current id
.
<template>
<div>
<div v-if="isPatientLoading">Loading</div>
<div v-else>{{ patient.name }}</div>
</div>
</template>
<script>
import { computed } from '@vue/composition-api'
import { useFind, useGet } from 'feathers-vuex'
export default {
name: 'PatientInfo',
props: {
id: {
type: String,
required: true
}
},
setup(props, context) {
const { Patient } = context.root.$FeathersVuex.api
const patientQueryWhen = computed(() => {
return !Patient.getFromStore(props.id)
})
const { item: patient, isPending: isPatientLoading } = useGet({
model: Patient,
id: props.id,
queryWhen: patientQueryWhen
})
return {
patient,
isPatientLoading
}
}
}
</script>
In the above example, the patientQueryWhen
computed property will return true
if we don't already have a Patient
record in the store with the current props.id
. While you could also achieve similar results by performing this logic inside of a params
computed property, the queryWhen
option works great as a "master override" to prevent unneeded queries.
# Routing with useGet
Apps will commonly have one or more routes with an :id
param. This might be for viewing or editing data. Vue Router has a feature that makes it easy to write reusable components without having to directly reference the $route
object. The key is to set the props
attribute in a route definition to true
. Here's an example route:
// router.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
name: 'Post View',
path: '/posts/:id',
component: () =>
import(/* webpackChunkName: "posts" */ './views/Post.vue'),
props: true
}
]
})
Now, the Post.vue
file only requires to have a prop
named id
. Vue Router will pass the params from the route as props to the component. See the first useGet example for a component that would work with the above route. The vue-router documentation has more information about Passing Props to Route Components (opens new window)
# Composing with Model types
Both useGet
and useFind
have an optional type parameter for the Model type which is used as the type for the returned item(s).
// Destructure Model class from global models object
const { User } = Vue.$FeathersVuex.api
// use useGet with User Model
useGet<typeof User.prototype>(/* ... */)
// use useFind with User Model
useFind<typeof User.prototype>(/* ... */)
# Conventions for Development
# Params are Computed
You might notice throughout these docs that the params are consistently shown as computed
properties. For long-term maintainability, this is the recommended practice.
Computed properties are read-only, so you can't push changes into them. This encourages declarative programming. Think of a declarative query as having all of the instructions it needs to pull in data from whatever sources are required to build the query object. Writing declarative params will assist you in avoiding complex conditional conflicts as queries become more complex.
In contrast, an imperatively-written query would be a reactive object that you directly modify. Think of imperative as pushing information into the query. eg: params.query.user = props.userId
. When you have a lot of imperative code pushing parameters into the query, it's really easy to create conflicting logic. So, keep in mind that while Feathers-Vuex will definitely handle an imperative-style query, your code will likely be less maintainable over the long run.
# Naming Variables
Having a variable naming convention can really assist the developer onboarding process and long run ease of use. Here are some guidelines that could prove useful while using the composition API utilities:
- Params for
useFind
result in a list of records, and should therefore indicate plurality. - When used, params for
useGet
result in a single record, and should indicate singularity.
import { computed } from '@vue/composition-api'
import { useFind, useGet } from 'feathers-vuex'
export default {
name: 'MyComponent',
props: {
id: {
type: String,
required: true
}
},
setup(props, context) {
const { Comment } = context.root.$FeathersVuex.api
// Plural "comments" in the params for useFind
const commentsParams = computed(() => {
return { query: {} }
})
const commentsData = useFind({
model: Comment,
params: commentsParams
})
const { items: comments } = commentsData
// Singular "comment" in the params for useGet
const commentParams = computed(() => {
return { query: {} }
})
const commentData = useGet({
model: Comment,
id: props.id,
params: commentParams
})
const { item: comment } = commentData
return {
comments,
comment
}
}
}
Variable naming becomes even more important when one service consumes the results of a previous service to make a query.
Note: the destructuring of commentsData
and commentData
, above, could happen on the same line as useFind
and useGet
, but it's a bit more clear in the example to split it into separate steps. For users who are accustomed to destructuring, it makes perfect sense to do so:
// Destructure and rename "item" to "comment" in the same line as the call to `useGet`
const { item: comment } = useGet({
model: Comment,
id: props.id,
params: commentParams
})
return { comment }