Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
36 changes: 36 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
- Database
- Cloud Firestore
- [`useFirestoreDoc`](#useFirestoreDoc)
- [`useFirestoreDocOnce`](#useFirestoreDocOnce)
- [`useFirestoreDocData`](#useFirestoreDocData)
- [`useFirestoreDocDataOnce`](#useFirestoreDocDataOnce)
- [`useFirestoreCollection`](#useFirestoreCollection)
- [`useFirestoreCollectionData`](#useFirestoreCollectionData)
- Realtime Database
Expand Down Expand Up @@ -191,6 +193,23 @@ _Throws a Promise by default_

[`DocumentSnapshot`](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot)

### `useFirestoreDocOnce`

Get a firestore document, but don't listen for updates.

_Throws a Promise by default_

#### Parameters

| Parameter | Type | Description |
| ----------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| ref | [`DocumentReference`](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentReference) | A reference to the document you want to listen to |
| options _?_ | ReactFireOptions | Options. This hook will not throw a Promise if you provide `startWithValue`. |

#### Returns

[`DocumentSnapshot`](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot)

### `useFirestoreDocData`

Listen to a Firestore Document.
Expand All @@ -208,6 +227,23 @@ _Throws a Promise by default_

`T`

### `useFirestoreDocDataOnce`

Get a firestore document, but don't listen for updates.

_Throws a Promise by default_

#### Parameters

| Parameter | Type | Description |
| ----------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| ref | [`DocumentReference`](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentReference) | A reference to the document you want to listen to |
| options _?_ | ReactFireOptions | Options. This hook will not throw a Promise if you provide `startWithValue`. |

#### Returns

`T`

### `useFirestoreCollection`

Listen to a Firestore Collection.
Expand Down
73 changes: 72 additions & 1 deletion reactfire/firestore/firestore.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
useFirestoreCollection,
FirebaseAppProvider,
useFirestoreCollectionData,
useFirestoreDocData
useFirestoreDocData,
useFirestoreDocDataOnce,
useFirestoreDocOnce
} from '..';
import { firestore } from 'firebase/app';

Expand Down Expand Up @@ -104,6 +106,75 @@ describe('Firestore', () => {
});
});

describe('useFirestoreDocOnce', () => {
it('works when the document does not exist, and does not update when it is created', async () => {
const ref = app
.firestore()
.collection('testDoc')
.doc('emptydoc');

const ReadFirestoreDoc = () => {
const dataOnce = useFirestoreDocOnce<any>(ref);

return (
<>
<h1 data-testid="once">{dataOnce.exists.toString()}</h1>
</>
);
};
const { getByTestId } = render(
<FirebaseAppProvider firebase={app}>
<React.Suspense fallback={<h1 data-testid="fallback">Fallback</h1>}>
<ReadFirestoreDoc />
</React.Suspense>
</FirebaseAppProvider>
);

await waitForElement(() => getByTestId('once'));
expect(getByTestId('once')).toContainHTML('false');

await act(() => ref.set({ a: 'test' }));
expect(getByTestId('once')).toContainHTML('false');
});
});

describe('useFirestoreDocDataOnce', () => {
it('does not update on database changes [TEST REQUIRES EMULATOR]', async () => {
const mockData1 = { a: 'hello' };
const mockData2 = { a: 'goodbye' };

const ref = app
.firestore()
.collection('testDoc')
.doc('readSuccess');

await ref.set(mockData1);

const ReadFirestoreDoc = () => {
const dataOnce = useFirestoreDocDataOnce<any>(ref, { idField: 'id' });

return (
<>
<h1 data-testid="once">{dataOnce.a}</h1>{' '}
</>
);
};
const { getByTestId } = render(
<FirebaseAppProvider firebase={app}>
<React.Suspense fallback={<h1 data-testid="fallback">Fallback</h1>}>
<ReadFirestoreDoc />
</React.Suspense>
</FirebaseAppProvider>
);

await waitForElement(() => getByTestId('once'));
expect(getByTestId('once')).toContainHTML(mockData1.a);

await act(() => ref.set(mockData2));
expect(getByTestId('once')).toContainHTML(mockData1.a);
});
});

describe('useFirestoreCollection', () => {
it('can get a Firestore collection [TEST REQUIRES EMULATOR]', async () => {
const mockData1 = { a: 'hello' };
Expand Down
35 changes: 35 additions & 0 deletions reactfire/firestore/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
checkStartWithValue
} from '..';
import { preloadObservable } from '../useObservable';
import { first } from 'rxjs/operators';

// starts a request for a firestore doc.
// imports the firestore SDK automatically
Expand Down Expand Up @@ -49,6 +50,23 @@ export function useFirestoreDoc<T = unknown>(
);
}

/**
* Get a firestore document and don't subscribe to changes
*
* @param ref - Reference to the document you want to get
* @param options
*/
export function useFirestoreDocOnce<T = unknown>(
ref: firestore.DocumentReference,
options?: ReactFireOptions<T>
): T extends {} ? T : firestore.DocumentSnapshot {
return useObservable(
doc(ref).pipe(first()),
`useFirestoreDocOnce:${ref.path}:${JSON.stringify(options)}`,
options ? options.startWithValue : undefined
);
}

/**
* Suscribe to Firestore Document changes
*
Expand All @@ -66,6 +84,23 @@ export function useFirestoreDocData<T = unknown>(
);
}

/**
* Get a firestore document and don't subscribe to changes
*
* @param ref - Reference to the document you want to get
* @param options
*/
export function useFirestoreDocDataOnce<T = unknown>(
ref: firestore.DocumentReference,
options?: ReactFireOptions<T>
): T {
return useObservable(
docData(ref, checkIdField(options)).pipe(first()),
`useFirestoreDocDataOnce:${ref.path}:${JSON.stringify(options)}`,
checkStartWithValue(options)
);
}

/**
* Subscribe to a Firestore collection
*
Expand Down
19 changes: 19 additions & 0 deletions sample/src/Firestore.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SuspenseWithPerf,
useFirestoreCollectionData,
useFirestoreDocData,
useFirestoreDocDataOnce,
useFirestore
} from 'reactfire';

Expand Down Expand Up @@ -32,6 +33,16 @@ const Counter = props => {
);
};

const StaticValue = props => {
const firestore = useFirestore();

const ref = firestore().doc('count/counter');

const { value } = useFirestoreDocDataOnce(ref);

return <span>{value}</span>;
};

const AnimalEntry = ({ saveAnimal }) => {
const [text, setText] = useState('');
const [disabled, setDisabled] = useState(false);
Expand Down Expand Up @@ -121,6 +132,14 @@ const SuspenseWrapper = props => {
>
<Counter />
</SuspenseWithPerf>
<h3>Sample One-time Get</h3>
<SuspenseWithPerf
fallback="connecting to Firestore..."
traceId="firestore-demo-doc"
>
<StaticValue />
</SuspenseWithPerf>

<h3>Sample Collection Listener</h3>
<SuspenseWithPerf
fallback="connecting to Firestore..."
Expand Down