Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
- [`useFirestoreDocDataOnce`](#useFirestoreDocDataOnce)
- [`useFirestoreCollection`](#useFirestoreCollection)
- [`useFirestoreCollectionData`](#useFirestoreCollectionData)
- [`useFirestoreCollectionDataOnce`](#useFirestoreCollectionDataOnce)
- Realtime Database
- [`useDatabaseObject`](#useDatabaseObject)
- [`useDatabaseObjectData`](#useDatabaseObjectData)
Expand Down Expand Up @@ -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.
Expand Down
72 changes: 72 additions & 0 deletions reactfire/firestore/firestore.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useFirestoreCollection,
FirebaseAppProvider,
useFirestoreCollectionData,
useFirestoreCollectionDataOnce,
useFirestoreDocData,
useFirestoreDocDataOnce,
useFirestoreDocOnce,
Expand Down Expand Up @@ -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<any>(ref, { idField: 'id' });

return (
<ul data-testid="readSuccess">
{list.map(item => (
<li key={item.id} data-testid="listItem">
{item.a}
</li>
))}
</ul>
);
};
const { queryAllByTestId, getByTestId } = render(
<FirebaseAppProvider firebase={app}>
<React.Suspense fallback={<h1 data-testid="fallback">Fallback</h1>}>
<ReadFirestoreCollection />
</React.Suspense>
</FirebaseAppProvider>
);

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<any>(ref, { idField: 'id' });

return (
<ul data-testid="readSuccess">
{list.map(item => (
<li key={item.id} data-testid="listItem">
{item.a}
</li>
))}
</ul>
);
};
const { getAllByTestId } = render(
<FirebaseAppProvider firebase={app}>
<React.Suspense fallback={<h1 data-testid="fallback">Fallback</h1>}>
<ReadFirestoreCollection />
</React.Suspense>
</FirebaseAppProvider>
);

await waitForElement(() => getAllByTestId('listItem'));
expect(getAllByTestId('listItem').length).toEqual(2);

await act(async () => await ref.add(mockData3));
expect(getAllByTestId('listItem').length).toEqual(2);
});
});
});
13 changes: 13 additions & 0 deletions reactfire/firestore/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,16 @@ export function useFirestoreCollectionData<T = { [key: string]: unknown }>(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<T = { [key: string]: unknown }>(query: firestore.Query, options?: ReactFireOptions<T[]>): T[] {
const idField = checkIdField(options);
const queryId = `firestore:collectionDataOnce:${getUniqueIdForFirestoreQuery(query)}:idField=${idField}`;

return useObservable(collectionData(query, idField).pipe(first()), queryId, checkStartWithValue(options));
Copy link
Collaborator

@davideast davideast May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky. I'm not sure if using first() to terminate an onSnapshot() listener is the right call when we could use .get() which is meant for one time reads.

What's tricky is that we don't support this natively in RxFire and ReactFire does not take a dependency of RxJS. This means I don't think we can directly create an observable from a promise like we would below in RxFire.

function getCollectionData(colRef) {
  return from(citiesCol.get());
}

So maybe we just add that to RxFire and then add that here? What are you thoughts? I'm happy to make the PR to RxFire or if you'd like to let me know as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the insight!

we could use .get() which is meant for one time reads.

I totally agree.

ReactFire does not take a dependency of RxJS.

Maybe I don't understand it properly, but it looks like it does have the dependency on rxjs.
So it looks like I could write the following if I want, but maybe it's just not a good thing design-wise for the reactfire and rxfire libraries (I'm not sure).

diff --git a/reactfire/firestore/index.tsx b/reactfire/firestore/index.tsx
index 0974b14..14971b1 100644
--- a/reactfire/firestore/index.tsx
+++ b/reactfire/firestore/index.tsx
@@ -1,9 +1,10 @@
 import { firestore } from 'firebase/app';
-import { collectionData, doc, docData, fromCollectionRef } from 'rxfire/firestore';
+import { collectionData, doc, docData, fromCollectionRef, snapToData } from 'rxfire/firestore';
 import { preloadFirestore, ReactFireOptions, useObservable, checkIdField, checkStartWithValue } from '..';
 import { preloadObservable } from '../useObservable';
-import { first } from 'rxjs/operators';
+import { map, first } from 'rxjs/operators';
 import { useFirebaseApp } from '../firebaseApp';
+import { from } from 'rxjs';
 
 const CACHED_QUERIES = '_reactFireFirestoreQueryCache';
 
@@ -124,6 +125,6 @@ export function useFirestoreCollectionData<T = { [key: string]: unknown }>(query
 export function useFirestoreCollectionDataOnce<T = { [key: string]: unknown }>(query: firestore.Query, options?: ReactFireOptions<T[]>): T[] {
   const idField = checkIdField(options);
   const queryId = `firestore:collectionDataOnce:${getUniqueIdForFirestoreQuery(query)}:idField=${idField}`;
-
-  return useObservable(collectionData(query, idField).pipe(first()), queryId, checkStartWithValue(options));
+  const onetimeCollectionData = from(query.get()).pipe(map(snapshot => snapshot.docs.map(snap => snapToData(snap, idField) as T)));
+  return useObservable(onetimeCollectionData, queryId, checkStartWithValue(options));
 }

If you'd like me to put some logic on the rxfire side, I'd be happy to do so! Please let me know. Thanks!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davideast and @banyan any update on this?
It would be a great addition to reactfire.

}
24 changes: 24 additions & 0 deletions sample/src/Firestore.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
AuthCheck,
SuspenseWithPerf,
useFirestoreCollectionData,
useFirestoreCollectionDataOnce,
useFirestoreDocData,
useFirestoreDocDataOnce,
useFirestore
Expand Down Expand Up @@ -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 (
<ul>
{animals.map(animal => (
<li key={animal.id}>{animal.commonName}</li>
))}
</ul>
);
};

const FavoriteAnimals = props => {
const firestore = useFirestore();
const baseRef = firestore.collection('animals');
Expand Down Expand Up @@ -147,6 +163,14 @@ const SuspenseWrapper = props => {
>
<FavoriteAnimals />
</SuspenseWithPerf>

<h3>Sample One-time Get Collection</h3>
<SuspenseWithPerf
fallback="connecting to Firestore..."
traceId="firestore-demo-collection"
>
<StaticList />
</SuspenseWithPerf>
</SuspenseList>
</AuthCheck>
</SuspenseWithPerf>
Expand Down