diff --git a/docs/reference.md b/docs/reference.md index 4d138b58..15d9da50 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -27,6 +27,7 @@ - [`useFirestoreDocDataOnce`](#useFirestoreDocDataOnce) - [`useFirestoreCollection`](#useFirestoreCollection) - [`useFirestoreCollectionData`](#useFirestoreCollectionData) + - [`useFirestoreCollectionDataOnce`](#useFirestoreCollectionDataOnce) - Realtime Database - [`useDatabaseObject`](#useDatabaseObject) - [`useDatabaseObjectData`](#useDatabaseObjectData) @@ -281,6 +282,23 @@ _Throws a Promise by default_ `T[]` +### `useFirestoreCollectionDataOnce` + +Get a firestore collection, but don't listen for updates. + +_Throws a Promise by default_ + +#### Parameters + +| Parameter | Type | Description | +| ----------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| ref | [`Query`](https://firebase.google.com/docs/reference/js/firebase.firestore.Query) | A query for the collection you want to listen to | +| options _?_ | ReactFireOptions | Options. This hook will not throw a Promise if you provide `startWithValue`. | + +#### Returns + +`T[]` + ### `useDatabaseObject` Listen to a Realtime Database Object. diff --git a/reactfire/firestore/firestore.test.tsx b/reactfire/firestore/firestore.test.tsx index 02ea892e..293c688a 100644 --- a/reactfire/firestore/firestore.test.tsx +++ b/reactfire/firestore/firestore.test.tsx @@ -8,6 +8,7 @@ import { useFirestoreCollection, FirebaseAppProvider, useFirestoreCollectionData, + useFirestoreCollectionDataOnce, useFirestoreDocData, useFirestoreDocDataOnce, useFirestoreDocOnce, @@ -387,4 +388,75 @@ describe('Firestore', () => { await waitForElement(() => getByTestId('rendered')); }); }); + + describe('useFirestoreCollectionDataOnce', () => { + it('works when the collection does not exist, and does not update when it is created', async () => { + const ref = app.firestore().collection('testDoc'); + + const ReadFirestoreCollection = () => { + const list = useFirestoreCollectionDataOnce(ref, { idField: 'id' }); + + return ( + + ); + }; + const { queryAllByTestId, getByTestId } = render( + + Fallback}> + + + + ); + + await waitForElement(() => getByTestId('readSuccess')); + expect(queryAllByTestId('listItem').length).toEqual(0); + + await act(async () => await ref.add({ a: 'test' })); + expect(queryAllByTestId('listItem').length).toEqual(0); + }); + + it('can get a Firestore collection and does not update on database changes[TEST REQUIRES EMULATOR]', async () => { + const mockData1 = { a: 'hello' }; + const mockData2 = { a: 'goodbye' }; + const mockData3 = { a: 'hi' }; + + const ref = app.firestore().collection('testCollection'); + + await act(async () => await ref.add(mockData1)); + await act(async () => await ref.add(mockData2)); + + const ReadFirestoreCollection = () => { + const list = useFirestoreCollectionDataOnce(ref, { idField: 'id' }); + + return ( + + ); + }; + const { getAllByTestId } = render( + + Fallback}> + + + + ); + + await waitForElement(() => getAllByTestId('listItem')); + expect(getAllByTestId('listItem').length).toEqual(2); + + await act(async () => await ref.add(mockData3)); + expect(getAllByTestId('listItem').length).toEqual(2); + }); + }); }); diff --git a/reactfire/firestore/index.tsx b/reactfire/firestore/index.tsx index 62f32462..0974b14e 100644 --- a/reactfire/firestore/index.tsx +++ b/reactfire/firestore/index.tsx @@ -114,3 +114,16 @@ export function useFirestoreCollectionData(query return useObservable(collectionData(query, idField), queryId, checkStartWithValue(options)); } + +/** + * Get a firestore collection and don't subscribe to changes + * + * @param ref - Reference to the collection you want to get + * @param options + */ +export function useFirestoreCollectionDataOnce(query: firestore.Query, options?: ReactFireOptions): T[] { + const idField = checkIdField(options); + const queryId = `firestore:collectionDataOnce:${getUniqueIdForFirestoreQuery(query)}:idField=${idField}`; + + return useObservable(collectionData(query, idField).pipe(first()), queryId, checkStartWithValue(options)); +} diff --git a/sample/src/Firestore.js b/sample/src/Firestore.js index 4267abf0..1c1e36f6 100644 --- a/sample/src/Firestore.js +++ b/sample/src/Firestore.js @@ -4,6 +4,7 @@ import { AuthCheck, SuspenseWithPerf, useFirestoreCollectionData, + useFirestoreCollectionDataOnce, useFirestoreDocData, useFirestoreDocDataOnce, useFirestore @@ -84,6 +85,21 @@ const List = ({ query, removeAnimal }) => { ); }; +const StaticList = () => { + const ref = useFirestore() + .collection('animals') + .orderBy('commonName', 'asc'); + const animals = useFirestoreCollectionDataOnce(ref, { idField: 'id' }); + + return ( +
    + {animals.map(animal => ( +
  • {animal.commonName}
  • + ))} +
+ ); +}; + const FavoriteAnimals = props => { const firestore = useFirestore(); const baseRef = firestore.collection('animals'); @@ -147,6 +163,14 @@ const SuspenseWrapper = props => { > + +

Sample One-time Get Collection

+ + +