From ffb24e77b62ac68def6f935f9114cd1a761d228f Mon Sep 17 00:00:00 2001 From: Kiyohiko Heima Date: Fri, 29 Jul 2022 23:39:55 +0900 Subject: [PATCH] feat(useFirestore): support reactive query (#2008) --- packages/firebase/useFirestore/index.md | 18 ++++++---- packages/firebase/useFirestore/index.test.ts | 27 ++++++++++++++ packages/firebase/useFirestore/index.ts | 38 ++++++++++++-------- 3 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 packages/firebase/useFirestore/index.test.ts diff --git a/packages/firebase/useFirestore/index.md b/packages/firebase/useFirestore/index.md index ff8c6a7deb9..0c0bac8742e 100644 --- a/packages/firebase/useFirestore/index.md +++ b/packages/firebase/useFirestore/index.md @@ -8,18 +8,24 @@ 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 @@ -27,7 +33,7 @@ const user = useFirestore(db.collection('users').doc('my-user-id')) 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 @@ -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')), ) ``` diff --git a/packages/firebase/useFirestore/index.test.ts b/packages/firebase/useFirestore/index.test.ts new file mode 100644 index 00000000000..5534ea5c944 --- /dev/null +++ b/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) + }) +}) diff --git a/packages/firebase/useFirestore/index.ts b/packages/firebase/useFirestore/index.ts index 8badcbcb496..49251adfe10 100644 --- a/packages/firebase/useFirestore/index.ts +++ b/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' @@ -33,24 +34,24 @@ function isDocumentReference(docRef: any): docRef is DocumentReference { } export function useFirestore( - docRef: DocumentReference, + maybeDocRef: MaybeRef>, initialValue: T, options?: UseFirestoreOptions ): Ref export function useFirestore( - docRef: Query, + maybeDocRef: MaybeRef>, initialValue: T[], options?: UseFirestoreOptions ): Ref // nullable initial values export function useFirestore( - docRef: DocumentReference, + maybeDocRef: MaybeRef>, initialValue?: T | undefined, options?: UseFirestoreOptions, ): Ref export function useFirestore( - docRef: Query, + maybeDocRef: MaybeRef>, initialValue?: T[], options?: UseFirestoreOptions ): Ref @@ -62,22 +63,27 @@ export function useFirestore( * @see https://vueuse.org/useFirestore */ export function useFirestore( - docRef: FirebaseDocRef, + maybeDocRef: MaybeRef>, initialValue: any = undefined, options: UseFirestoreOptions = {}, ) { const { - errorHandler = (err: Error) => console.error(err), autoDispose = true, } = options - if (isDocumentReference(docRef)) { + const refOfDocRef = isRef(maybeDocRef) ? maybeDocRef : computed(() => maybeDocRef) + + if (isDocumentReference(refOfDocRef.value)) { const data = ref(initialValue) as Ref + let close = () => { } - const close = onSnapshot(docRef, (snapshot) => { - data.value = getData(snapshot) || null - }, errorHandler) + watch(refOfDocRef, (docRef) => { + close() + close = onSnapshot(docRef as DocumentReference, (snapshot) => { + data.value = getData(snapshot) || null + }, errorHandler) + }, { immediate: true }) tryOnScopeDispose(() => { close() @@ -87,10 +93,14 @@ export function useFirestore( } else { const data = ref(initialValue) as Ref + 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, (snapshot) => { + data.value = snapshot.docs.map(getData).filter(isDef) + }, errorHandler) + }, { immediate: true }) if (autoDispose) { tryOnScopeDispose(() => {