Skip to content

Commit

Permalink
feat(useFirestore): support reactive query (#2008)
Browse files Browse the repository at this point in the history
  • Loading branch information
kiyopikko committed Jul 29, 2022
1 parent b916a10 commit ffb24e7
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 20 deletions.
18 changes: 12 additions & 6 deletions packages/firebase/useFirestore/index.md
Expand Up @@ -8,26 +8,32 @@ Reactive [Firestore](https://firebase.google.com/docs/firestore) binding. Making

## Usage

```js {7,9}
```js {9,12,17}
import { computed, ref } from 'vue'
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'
import { collection, doc, getFirestore, limit, orderBy, query } from 'firebase/firestore'
import { useFirestore } from '@vueuse/firebase/useFirestore'

const app = initializeApp({ projectId: 'MY PROJECT ID' })
const db = getFirestore(app)

const todos = useFirestore(db.collection('todos'))
const todos = useFirestore(collection(db, 'todos'))

// or for doc reference
const user = useFirestore(db.collection('users').doc('my-user-id'))
const user = useFirestore(doc(db, 'users', 'my-user-id'))

// you can also use ref value for reactive query
const postLimit = ref(10)
const postsQuery = computed(() => query(collection(db, 'posts'), orderBy('createdAt', 'desc'), limit(postLimit.value)))
const posts = useFirestore(postsQuery)
```

## Share across instances

You can reuse the db reference by passing `autoDispose: false`

```ts
const todos = useFirestore(db.collection('todos'), undefined, { autoDispose: false })
const todos = useFirestore(collection(db, 'todos'), undefined, { autoDispose: false })
```

or use `createGlobalState` from the core package
Expand All @@ -38,7 +44,7 @@ import { createGlobalState } from '@vueuse/core'
import { useFirestore } from '@vueuse/firebase/useFirestore'

export const useTodos = createGlobalState(
() => useFirestore(db.collection('todos')),
() => useFirestore(collection(db, 'todos')),
)
```

Expand Down
27 changes: 27 additions & 0 deletions packages/firebase/useFirestore/index.test.ts
@@ -0,0 +1,27 @@
import { ref } from 'vue-demi'
import { onSnapshot } from 'firebase/firestore'
import type { Mock } from 'vitest'
import { useFirestore } from './index'

vi.mock('firebase/firestore', () => ({
onSnapshot: vi.fn(),
}))

describe('useFirestore', () => {
beforeEach(() => {
(onSnapshot as Mock).mockClear()
})

it('should call onSnapshot with document reference', () => {
const docRef = { path: 'users' } as any
useFirestore(docRef)
expect((onSnapshot as Mock).mock.calls[0][0]).toStrictEqual(docRef)
})

it('should call onSnapshot with ref value of document reference', () => {
const docRef = { path: 'posts' } as any
const refOfDocRef = ref(docRef)
useFirestore(refOfDocRef)
expect((onSnapshot as Mock).mock.calls[0][0]).toStrictEqual(docRef)
})
})
38 changes: 24 additions & 14 deletions packages/firebase/useFirestore/index.ts
@@ -1,6 +1,7 @@
import type { Ref } from 'vue-demi'
import { ref } from 'vue-demi'
import { computed, isRef, ref, watch } from 'vue-demi'
import type { DocumentData, DocumentReference, DocumentSnapshot, Query, QueryDocumentSnapshot } from 'firebase/firestore'
import type { MaybeRef } from '@vueuse/shared'
import { isDef, tryOnScopeDispose } from '@vueuse/shared'
import { onSnapshot } from 'firebase/firestore'

Expand Down Expand Up @@ -33,24 +34,24 @@ function isDocumentReference<T>(docRef: any): docRef is DocumentReference<T> {
}

export function useFirestore<T extends DocumentData>(
docRef: DocumentReference<T>,
maybeDocRef: MaybeRef<DocumentReference<T>>,
initialValue: T,
options?: UseFirestoreOptions
): Ref<T | null>
export function useFirestore<T extends DocumentData>(
docRef: Query<T>,
maybeDocRef: MaybeRef<Query<T>>,
initialValue: T[],
options?: UseFirestoreOptions
): Ref<T[]>

// nullable initial values
export function useFirestore<T extends DocumentData>(
docRef: DocumentReference<T>,
maybeDocRef: MaybeRef<DocumentReference<T>>,
initialValue?: T | undefined,
options?: UseFirestoreOptions,
): Ref<T | undefined | null>
export function useFirestore<T extends DocumentData>(
docRef: Query<T>,
maybeDocRef: MaybeRef<Query<T>>,
initialValue?: T[],
options?: UseFirestoreOptions
): Ref<T[] | undefined>
Expand All @@ -62,22 +63,27 @@ export function useFirestore<T extends DocumentData>(
* @see https://vueuse.org/useFirestore
*/
export function useFirestore<T extends DocumentData>(
docRef: FirebaseDocRef<T>,
maybeDocRef: MaybeRef<FirebaseDocRef<T>>,
initialValue: any = undefined,
options: UseFirestoreOptions = {},
) {
const {

errorHandler = (err: Error) => console.error(err),
autoDispose = true,
} = options

if (isDocumentReference<T>(docRef)) {
const refOfDocRef = isRef(maybeDocRef) ? maybeDocRef : computed(() => maybeDocRef)

if (isDocumentReference<T>(refOfDocRef.value)) {
const data = ref(initialValue) as Ref<T | null | undefined>
let close = () => { }

const close = onSnapshot(docRef, (snapshot) => {
data.value = getData(snapshot) || null
}, errorHandler)
watch(refOfDocRef, (docRef) => {
close()
close = onSnapshot(docRef as DocumentReference<T>, (snapshot) => {
data.value = getData(snapshot) || null
}, errorHandler)
}, { immediate: true })

tryOnScopeDispose(() => {
close()
Expand All @@ -87,10 +93,14 @@ export function useFirestore<T extends DocumentData>(
}
else {
const data = ref(initialValue) as Ref<T[] | undefined>
let close = () => { }

const close = onSnapshot(docRef, (snapshot) => {
data.value = snapshot.docs.map(getData).filter(isDef)
}, errorHandler)
watch(refOfDocRef, (docRef) => {
close()
close = onSnapshot(docRef as Query<T>, (snapshot) => {
data.value = snapshot.docs.map(getData).filter(isDef)
}, errorHandler)
}, { immediate: true })

if (autoDispose) {
tryOnScopeDispose(() => {
Expand Down

0 comments on commit ffb24e7

Please sign in to comment.