Skip to content

Commit a687167

Browse files
committed
Allow setting local:true on useFind options
local: true will prevent API requests from being made
1 parent cdcb753 commit a687167

File tree

3 files changed

+139
-34
lines changed

3 files changed

+139
-34
lines changed

docs/composition-api.md

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,14 @@ Before you can use the `useFind` and `useGet` composition functions, you'll need
1212

1313
## useFind <Badge text="3.0.0+" />
1414

15-
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.
16-
17-
1. Instead of providing a service name, you provide a service Model from the `$FeathersVuex` Vue plugin.
18-
1. 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 `{ lazy: true }` in the `useFind` options, which will be passed directly to the internal `watch` on the params.
19-
20-
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`, as shown in the following example:
15+
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. Assume that each `Tutorial` record has a `name` property. We're not going to style any of this, so the `<style>` tags are removed from the examples.
2116

2217
```html
2318
<template>
2419
<div>
25-
<pre> {{ todos }} </pre>
20+
<li v-for="tutorial in tutorials" :key="tutorial._id">
21+
{{ tutorial.name }}
22+
</li>
2623
</div>
2724
</template>
2825

@@ -33,27 +30,100 @@ import { useFind } from 'feathers-vuex'
3330
export default {
3431
name: 'UserGuide',
3532
setup(props, context) {
36-
// Get a reference to the desired Model class.
37-
const { Todo } = context.root.$FeathersVuex.spark
33+
const { Tutorial } = context.root.$FeathersVuex.spark
3834
39-
// Setup params with a query
40-
const todosParams = computed(() => {
35+
const tutorialsParams = computed(() => {
4136
return {
4237
query: { isComplete: false }
4338
}
4439
})
40+
const tutorialsData = useFind({ model: Tutorial, params: tutorialsParams })
4541
46-
// Provide the model and params to the useFind utility
47-
const { todos } = useFind({ model: Todo, params: todosParams })
48-
49-
return { todos }
42+
return {
43+
tutorials: tutorialsData.items
44+
}
5045
}
5146
}
5247
</script>
48+
```
49+
50+
### Options
51+
52+
Let's look at the `UseFindOptions` TypeScript interface to see what else we can pass in the options. If this is your first time, let's do a quick primer for the below interface as an alternative to reading the [TypeScript interface docs](https://www.typescriptlang.org/docs/handbook/interfaces.html):
53+
54+
- `UseFindOptions` is the name of the interface, similar to naming any other variable.
55+
- Each line of the interface documents a property.
56+
- The string before the `:` is the name of the property.
57+
- Everything after the `:` describes what type of variable it is.
58+
- You can look at any `|` after the `:` as a conditional "or"
59+
- Any property not followed by a `?` is required.
60+
- Any property followed by a `?` is optional.
61+
62+
```ts
63+
interface UseFindOptions {
64+
model: Function
65+
params: Params | Ref<Params>
66+
fetchParams?: Params | Ref<Params>
67+
queryWhen?: Ref<Function>
68+
qid?: string
69+
lazy?: boolean
70+
}
71+
```
5372

54-
<style lang="postcss"></style>
73+
Let's look at each one individually:
74+
75+
- `model` must be a Feathers-Vuex Model class. The Model's `find` and `findInStore` methods are used to query data.
76+
- `params` is a FeathersJS Params object OR a Composition API `ref` (or `computed`, since they return a `ref` instance) which returns a Params object.
77+
- When provided alone (without the options `fetchParams`), this same query is used for both the local data store and the API requests.
78+
- Explicitly returning `null` will prevent an API request from being made.
79+
- You can use `params.qid` to dynamically specify the query identifier for any API request.
80+
- Set `params.paginate` to `true` to turn off realtime updates for the results and defer pagination to the API server.
81+
- 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.
82+
= `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.
83+
- Explicitly returning `null` will prevent an API request from being made.
84+
- `queryWhen` must be a `computed` property which returns a `boolean`. It provides a logical separation for preventing API requests *outside* of the `params`.
85+
- `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.
86+
- `lazy`, which is `false` by default, determines if the internal `watch` should fire immediately. Set `lazy: true` and the query will not fire immediately. It will only fire on subsequent changes in the params.
87+
88+
### Returned Utilities
89+
90+
Notice the `tutorialsData` in the previous example. You can see that there's an `items` property, which is returned from the `setup` method as the `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.
91+
92+
```ts
93+
interface UseFindData {
94+
items: Ref<any>
95+
servicePath: Ref<string>
96+
isFindPending: Ref<boolean>
97+
haveBeenRequestedOnce: Ref<boolean>
98+
haveLoadedOnce: Ref<boolean>
99+
isLocal: Ref<boolean>
100+
qid: Ref<string>
101+
debounceTime: Ref<number>
102+
latestQuery: Ref<object>
103+
paginationData: Ref<object>
104+
error: Ref<Error>
105+
find: Function
106+
}
55107
```
56108

109+
Let's look at at what functionality each provides:
110+
111+
- `items` is the list of results
112+
113+
### Renaming attributes
114+
115+
The `useFind` utility will always return an
116+
They can be renamed in the same way that you would do it with any object.
117+
118+
### Compared to `makeFindMixin`
119+
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.
120+
121+
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.
122+
1. Instead of providing a service name, you provide a service Model from the `$FeathersVuex` Vue plugin.
123+
1. 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 `{ lazy: true }` in the `useFind` options, which will be passed directly to the internal `watch` on the params.
124+
125+
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`, as shown in the following example:
126+
57127
Notice in the above example that the params are provided as a computed property. For long-term maintainability, this is the recommended practice. It encourages you to think about your queries declaratively. 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. Declaratively written queries will likely assist you in avoiding conflicting code as conditions for making queries become more complex.
58128

59129
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 probably be less maintainable over the long run.

src/useFind.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface UseFindOptions {
1919
fetchParams?: Params | Ref<Params>
2020
queryWhen?: Ref<Function>
2121
qid?: string
22+
local?: boolean
2223
lazy?: boolean
2324
}
2425
interface UseFindState {
@@ -43,6 +44,7 @@ interface UseFindData {
4344
latestQuery: Ref<object>
4445
paginationData: Ref<object>
4546
error: Ref<Error>
47+
find: Function
4648
}
4749

4850
export default function find(options: UseFindOptions): UseFindData {
@@ -175,6 +177,7 @@ export default function find(options: UseFindOptions): UseFindData {
175177

176178
return {
177179
...computes,
178-
...toRefs(state)
180+
...toRefs(state),
181+
find
179182
}
180183
}

test/use/find.test.ts

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,27 @@ eslint
44
@typescript-eslint/no-explicit-any: 0,
55
@typescript-eslint/no-empty-function: 0
66
*/
7+
import Vue from 'vue'
8+
import VueCompositionApi from '@vue/composition-api'
9+
Vue.use(VueCompositionApi)
10+
711
import jsdom from 'jsdom-global'
812
import { assert } from 'chai'
913
import feathersVuex, { FeathersVuex } from '../../src/index'
1014
import { feathersRestClient as feathersClient } from '../fixtures/feathers-client'
1115
import useFind from '../../src/useFind'
12-
import Vue from 'vue/dist/vue'
13-
import VueCompositionApi from '@vue/composition-api'
1416
import Vuex from 'vuex'
1517
// import { shallowMount } from '@vue/test-utils'
1618
import { computed, isRef } from '@vue/composition-api'
1719
jsdom()
1820
require('events').EventEmitter.prototype._maxListeners = 100
1921

22+
Vue.use(Vuex)
23+
Vue.use(FeathersVuex)
24+
2025
function makeContext() {
2126
const { makeServicePlugin, BaseModel } = feathersVuex(feathersClient, {
22-
serverAlias: 'make-find-mixin'
27+
serverAlias: 'useFind'
2328
})
2429

2530
class Instrument extends BaseModel {
@@ -38,10 +43,6 @@ function makeContext() {
3843
return { store, Instrument, BaseModel, makeServicePlugin }
3944
}
4045

41-
Vue.use(VueCompositionApi)
42-
Vue.use(Vuex)
43-
Vue.use(FeathersVuex)
44-
4546
describe('use/find', function() {
4647
it('returns correct default data', function() {
4748
const { Instrument } = makeContext()
@@ -60,17 +61,14 @@ describe('use/find', function() {
6061
const {
6162
debounceTime,
6263
error,
63-
find,
64-
findInStore,
6564
haveBeenRequestedOnce,
6665
haveLoadedOnce,
6766
isFindPending,
6867
isLocal,
6968
items,
7069
latestQuery,
7170
paginationData,
72-
qid,
73-
queryWhen
71+
qid
7472
} = instrumentsData
7573

7674
assert(isRef(debounceTime))
@@ -79,9 +77,6 @@ describe('use/find', function() {
7977
assert(isRef(error))
8078
assert(error.value === null)
8179

82-
assert(typeof find === 'function')
83-
assert(typeof findInStore === 'function')
84-
8580
assert(isRef(haveBeenRequestedOnce))
8681
assert(haveBeenRequestedOnce.value === true)
8782

@@ -109,9 +104,6 @@ describe('use/find', function() {
109104

110105
assert(isRef(qid))
111106
assert(qid.value === 'default')
112-
113-
assert(isRef(queryWhen))
114-
assert(queryWhen.value === true)
115107
})
116108

117109
it('allows passing {lazy:true} to not query immediately', function() {
@@ -133,4 +125,44 @@ describe('use/find', function() {
133125
assert(isRef(haveBeenRequestedOnce))
134126
assert(haveBeenRequestedOnce.value === false)
135127
})
128+
129+
it('params can return null to prevent the query', function() {
130+
const { Instrument } = makeContext()
131+
132+
const instrumentParams = computed(() => {
133+
return null
134+
})
135+
const instrumentsData = useFind({
136+
model: Instrument,
137+
params: instrumentParams,
138+
lazy: true
139+
})
140+
const { haveBeenRequestedOnce } = instrumentsData
141+
142+
assert(isRef(haveBeenRequestedOnce))
143+
assert(haveBeenRequestedOnce.value === false)
144+
})
145+
146+
it('allows using `local: true` to prevent API calls from being made', function() {
147+
const { Instrument } = makeContext()
148+
149+
const instrumentParams = computed(() => {
150+
return {
151+
query: {}
152+
}
153+
})
154+
const instrumentsData = useFind({
155+
model: Instrument,
156+
params: instrumentParams,
157+
local: true
158+
})
159+
const { haveBeenRequestedOnce, find } = instrumentsData
160+
161+
assert(isRef(haveBeenRequestedOnce))
162+
assert(haveBeenRequestedOnce.value === false, 'no request during init')
163+
164+
find()
165+
166+
assert(haveBeenRequestedOnce.value === false, 'no request after find')
167+
})
136168
})

0 commit comments

Comments
 (0)