Skip to content

Commit 9ed2e15

Browse files
committed
Example showing “loading” states
1 parent b243919 commit 9ed2e15

File tree

3 files changed

+227
-37
lines changed

3 files changed

+227
-37
lines changed

docs/composition-api.md

Lines changed: 195 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default {
3434
name: 'UserGuide',
3535
setup(props, context) {
3636
// 1. Get a reference to the model class
37-
const { Tutorial } = context.root.$FeathersVuex.spark
37+
const { Tutorial } = context.root.$FeathersVuex.api
3838
3939
// 2. Create a computed property for the params
4040
const tutorialsParams = computed(() => {
@@ -111,7 +111,7 @@ interface UseFindData {
111111
servicePath: Ref<string>
112112
isFindPending: Ref<boolean>
113113
haveBeenRequestedOnce: Ref<boolean>
114-
haveLoadedOnce: Ref<boolean>
114+
haveLoaded: Ref<boolean>
115115
isLocal: Ref<boolean>
116116
qid: Ref<string>
117117
debounceTime: Ref<number>
@@ -128,7 +128,7 @@ Let's look at the functionality that each one provides:
128128
- `servicePath` is the FeathersJS service path that is used by the current model. This is mostly only useful for debugging.
129129
- `isFindPending` 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.
130130
- `haveBeenRequestedOnce` 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.
131-
- `haveLoadedOnce` 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.
131+
- `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.
132132
- `isLocal` is a boolean that is set to true if this data is local only.
133133
- `qid` is currently the primary `qid` provided in params. It might become more useful in the future.
134134
- `debounceTime` is the current number of milliseconds used as the debounce interval.
@@ -157,7 +157,7 @@ import { useFind } from 'feathers-vuex'
157157
export default {
158158
name: 'UserGuide',
159159
setup(props, context) {
160-
const { Todo } = context.root.$FeathersVuex.spark
160+
const { Todo } = context.root.$FeathersVuex.api
161161
162162
const todosParams = computed(() => {
163163
return {
@@ -196,7 +196,197 @@ The `useGet` utility is still being built. Docs will be written when it becomes
196196

197197
### Returned Attributes
198198

199-
## Pairing `useFind` with `useGet`
199+
## Pattens: `useFind` with `useGet`
200+
201+
### Simultaneous Queries
202+
203+
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`.
204+
205+
```html
206+
<template>
207+
<div>
208+
<div>{{ patient.name }}</div>
209+
210+
<li v-for="appointment in appointments" :key="appointment._id">
211+
{{ appointment.date }}
212+
</li>
213+
</div>
214+
</template>
215+
216+
<script>
217+
import { computed } from '@vue/composition-api'
218+
import { useFind, useGet } from 'feathers-vuex'
219+
220+
export default {
221+
name: 'PatientAppointments',
222+
props: {
223+
id: {
224+
type: String,
225+
required: true
226+
}
227+
},
228+
setup(props, context) {
229+
const { Patient, Appointment } = context.root.$FeathersVuex.api
230+
231+
// Get the patient record
232+
const { item: patient } = useGet({ model: Patient, id: props.id })
233+
234+
// Get all of the appointments belonging to the current patient
235+
const appointmentsParams = computed(() => {
236+
return {
237+
query: {
238+
userId: props.id,
239+
$sort: { date: -1 }
240+
}
241+
}
242+
})
243+
const { items: appointments } = useFind({
244+
model: Appointment,
245+
params: appointmentsParams
246+
})
247+
248+
return {
249+
patient,
250+
appointments
251+
}
252+
}
253+
}
254+
```
255+
256+
### Deferring Queries
257+
258+
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:
259+
260+
```html
261+
<template>
262+
<div>
263+
<div>{{ patient.name }}</div>
264+
265+
<li v-for="appointment in appointments" :key="appointment._id">
266+
{{ appointment.date }}
267+
</li>
268+
269+
<div v-if="!appointments.length && haveLoaded">
270+
No appointments have been scheduled for this patient.
271+
</div>
272+
</div>
273+
</template>
274+
275+
<script>
276+
import { computed } from '@vue/composition-api'
277+
import { useFind, useGet } from 'feathers-vuex'
278+
279+
export default {
280+
name: 'PatientAppointments',
281+
props: {
282+
id: {
283+
type: String,
284+
required: true
285+
}
286+
},
287+
setup(props, context) {
288+
const { Patient, Appointment } = context.root.$FeathersVuex.api
289+
290+
// Get the patient record
291+
const { item: patient } = useGet({ model: Patient, id: props.id })
292+
293+
// Get all of the appointments belonging to the current patient
294+
const appointmentsParams = computed(() => {
295+
// (1)
296+
if (!patient.value) {
297+
return null
298+
}
299+
// (2)
300+
return {
301+
query: {
302+
userId: patient.value._id,
303+
$sort: { date: -1 }
304+
}
305+
}
306+
})
307+
const { items: appointments, haveLoaded } = useFind({
308+
model: Appointment,
309+
params: appointmentsParams
310+
})
311+
312+
return {
313+
patient,
314+
appointments,
315+
haveLoaded
316+
}
317+
}
318+
}
319+
```
320+
321+
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.
322+
323+
Once the `patient` has loaded, the full params object is returned at comment `(2)`. This allows the `useFind` utility to make the request.
324+
325+
### Showing Loading State
326+
327+
This next example builds on the previous one and adds loading state for both the `patient` and the `appointments`.
328+
329+
```html
330+
<template>
331+
<div>
332+
<div v-if="isPatientLoading">Loading</div>
333+
<div v-else>{{ patient.name }}</div>
334+
335+
<li v-for="appointment in appointments" :key="appointment._id">
336+
{{ appointment.date }}
337+
</li>
338+
339+
<div v-if="!appointments.length && haveLoaded">
340+
No appointments have been scheduled for this patient.
341+
</div>
342+
</div>
343+
</template>
344+
345+
<script>
346+
import { computed } from '@vue/composition-api'
347+
import { useFind, useGet } from 'feathers-vuex'
348+
349+
export default {
350+
name: 'PatientAppointments',
351+
props: {
352+
id: {
353+
type: String,
354+
required: true
355+
}
356+
},
357+
setup(props, context) {
358+
const { Patient, Appointment } = context.root.$FeathersVuex.api
359+
360+
const {
361+
item: patient,
362+
isPending: isPatientLoading
363+
} = useGet({ model: Patient, id: props.id })
364+
365+
const appointmentsParams = computed(() => {
366+
if (!patient.value) {
367+
return null
368+
}
369+
return {
370+
query: {
371+
userId: patient.value._id,
372+
$sort: { date: -1 }
373+
}
374+
}
375+
})
376+
const { items: appointments, haveLoaded } = useFind({
377+
model: Appointment,
378+
params: appointmentsParams
379+
})
380+
381+
return {
382+
patient,
383+
isPatientLoading,
384+
appointments,
385+
haveLoaded
386+
}
387+
}
388+
}
389+
```
200390
201391
## Conventions for Development
202392

src/useFind.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ interface UseFindOptions {
2525
interface UseFindState {
2626
debounceTime: null | number
2727
qid: string
28-
isFindPending: boolean
29-
haveBeenRequestedOnce: boolean
30-
haveLoadedOnce: boolean
28+
isPending: boolean
29+
haveBeenRequested: boolean
30+
haveLoaded: boolean
3131
error: null | Error
3232
latestQuery: null | object
3333
isLocal: boolean
3434
}
3535
interface UseFindData {
3636
items: Ref<any>
3737
servicePath: Ref<string>
38-
isFindPending: Ref<boolean>
39-
haveBeenRequestedOnce: Ref<boolean>
40-
haveLoadedOnce: Ref<boolean>
38+
isPending: Ref<boolean>
39+
haveBeenRequested: Ref<boolean>
40+
haveLoaded: Ref<boolean>
4141
isLocal: Ref<boolean>
4242
qid: Ref<string>
4343
debounceTime: Ref<number>
@@ -84,9 +84,9 @@ export default function find(options: UseFindOptions): UseFindData {
8484

8585
const state = reactive<UseFindState>({
8686
qid,
87-
isFindPending: false,
88-
haveBeenRequestedOnce: false,
89-
haveLoadedOnce: false,
87+
isPending: false,
88+
haveBeenRequested: false,
89+
haveLoaded: false,
9090
error: null,
9191
debounceTime: null,
9292
latestQuery: null,
@@ -128,20 +128,20 @@ export default function find(options: UseFindOptions): UseFindData {
128128
function find<T>(params: Params): T {
129129
params = isRef(params) ? params.value : params
130130
if (queryWhen.value && !state.isLocal) {
131-
state.isFindPending = true
132-
state.haveBeenRequestedOnce = true
131+
state.isPending = true
132+
state.haveBeenRequested = true
133133

134134
return model.find(params).then(response => {
135135
// To prevent thrashing, only clear error on response, not on initial request.
136136
state.error = null
137-
state.haveLoadedOnce = true
137+
state.haveLoaded = true
138138

139139
const queryInfo = getQueryInfo(params, response)
140140
queryInfo.response = response
141141
queryInfo.isOutdated = false
142142

143143
state.latestQuery = queryInfo
144-
state.isFindPending = false
144+
state.isPending = false
145145
return response
146146
})
147147
}

test/use/find.test.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ describe('use/find', function() {
6161
const {
6262
debounceTime,
6363
error,
64-
haveBeenRequestedOnce,
65-
haveLoadedOnce,
66-
isFindPending,
64+
haveBeenRequested,
65+
haveLoaded,
66+
isPending,
6767
isLocal,
6868
items,
6969
latestQuery,
@@ -77,14 +77,14 @@ describe('use/find', function() {
7777
assert(isRef(error))
7878
assert(error.value === null)
7979

80-
assert(isRef(haveBeenRequestedOnce))
81-
assert(haveBeenRequestedOnce.value === true)
80+
assert(isRef(haveBeenRequested))
81+
assert(haveBeenRequested.value === true)
8282

83-
assert(isRef(haveLoadedOnce))
84-
assert(haveLoadedOnce.value === false)
83+
assert(isRef(haveLoaded))
84+
assert(haveLoaded.value === false)
8585

86-
assert(isRef(isFindPending))
87-
assert(isFindPending.value === true)
86+
assert(isRef(isPending))
87+
assert(isPending.value === true)
8888

8989
assert(isRef(isLocal))
9090
assert(isLocal.value === false)
@@ -120,10 +120,10 @@ describe('use/find', function() {
120120
params: instrumentParams,
121121
lazy: true
122122
})
123-
const { haveBeenRequestedOnce } = instrumentsData
123+
const { haveBeenRequested } = instrumentsData
124124

125-
assert(isRef(haveBeenRequestedOnce))
126-
assert(haveBeenRequestedOnce.value === false)
125+
assert(isRef(haveBeenRequested))
126+
assert(haveBeenRequested.value === false)
127127
})
128128

129129
it('params can return null to prevent the query', function() {
@@ -137,10 +137,10 @@ describe('use/find', function() {
137137
params: instrumentParams,
138138
lazy: true
139139
})
140-
const { haveBeenRequestedOnce } = instrumentsData
140+
const { haveBeenRequested } = instrumentsData
141141

142-
assert(isRef(haveBeenRequestedOnce))
143-
assert(haveBeenRequestedOnce.value === false)
142+
assert(isRef(haveBeenRequested))
143+
assert(haveBeenRequested.value === false)
144144
})
145145

146146
it('allows using `local: true` to prevent API calls from being made', function() {
@@ -156,13 +156,13 @@ describe('use/find', function() {
156156
params: instrumentParams,
157157
local: true
158158
})
159-
const { haveBeenRequestedOnce, find } = instrumentsData
159+
const { haveBeenRequested, find } = instrumentsData
160160

161-
assert(isRef(haveBeenRequestedOnce))
162-
assert(haveBeenRequestedOnce.value === false, 'no request during init')
161+
assert(isRef(haveBeenRequested))
162+
assert(haveBeenRequested.value === false, 'no request during init')
163163

164164
find()
165165

166-
assert(haveBeenRequestedOnce.value === false, 'no request after find')
166+
assert(haveBeenRequested.value === false, 'no request after find')
167167
})
168168
})

0 commit comments

Comments
 (0)