# 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:

  1. 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 at context.root.$FeathersVuex
  2. 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's find and findInStore methods are used to query data.
  • params is a FeathersJS Params object OR a Composition API ref (or computed, since they return a ref 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. The qid is used for tracking pagination data and enabling the fall-through cache across multiple queries.
    • Set params.paginate to true 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, setting debounce: 1000 will assure that the API request will be made at most every 1 second.
    • Set params.temps to true 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 to true to include cloned items in the results. The queried items get replaced with the corresponding copies from copiesById
  • fetchParams This is a separate set of params that, when provided, will become the params sent to the API server. The params will then only be used to query data from the local data store.
    • Explicitly returning null will prevent an API request from being made.
  • queryWhen must be a computed property which returns a boolean. It provides a logical separation for preventing API requests outside of the params.
  • qid allows you to specify a query identifier (used in the pagination data in the store). This can also be set dynamically by returning a qid in the params.
  • immediate, which is true by default, determines if the internal watch should fire immediately. Set immediate: 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 to true just before each outgoing request. It is set to false 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 to true 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 remains true 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 primary qid 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 the paginationData.
  • 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 have paginate: 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.

  1. 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 from useFind are always consistent.
  2. Instead of providing a service name, you provide a service Model from the $FeathersVuex Vue plugin.
  3. The default behavior of useFind is to immediately query the API server. The makeFindMixin, by default, would wait until the watcher noticed the change. This is to match the default behavior of watch in the Vue Composition API. You can pass { immediate: false } in the useFind options, which will be passed directly to the internal watch 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's get and getFromStore 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 (unless queryWhen evaluates to false).
    • If the id is null, no query will be made.
  • params is a FeathersJS Params object OR a Composition API ref (or computed, since they return a ref instance) which returns a Params object.
    • Unlike the useFind utility, useGet does not currently have built-in debouncing.
  • queryWhen must be a computed property which returns a boolean. It provides a logical separation for preventing API requests apart from null in the id.
  • immediate, which is true by default, determines if the internal watch should fire immediately. Set immediate: false and the query will not fire immediately. It will only fire on subsequent changes to the id or params.

# 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

  1. Create a pagination ref containing an object with $limit and $skip properties.
  2. Create a params computed property. Be sure to include a query object and paginate: true in the params. It's also recommended that you use a qid to identify the component that you're using, otherwise pagination data could mix with other parts of the UI and create an inconsistent experience.
  3. Merge the pagination into the params.query.
  4. Pass the params object to the useFind utility.
  5. Pull the items array and latestQuery object from useFind's return value.
  6. Make the items, latestQuery, and pagination available to the component's template by returning them in the setup method.
  7. Provide the above two variables to the FeathersVuexPagination component as props.
  8. Provide a UI inside the default slot, binding to variables and desired events.

# Props

The FeathersVuexPagination component only accepts two props.

  • v-model (or value): Receives the pagination object, which must contain the $limit and $skip properties.
  • latest-query, which receives the latestQuery object returned by the useFind 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 the v-model.
  • pageCount {Number} If the response from the API server includes a total attribute, the pageCount 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 }