Skip to content
Merged
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
72 changes: 72 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@
- Database
- Cloud Firestore
- [`useFirestoreDoc`](#useFirestoreDoc)
- [`useFirestoreDocData`](#useFirestoreDocData)
- [`useFirestoreCollection`](#useFirestoreCollection)
- [`useFirestoreCollectionData`](#useFirestoreCollectionData)
- Realtime Database
- [`useDatabaseObject`](#useDatabaseObject)
- [`useDatabaseObjectData`](#useDatabaseObjectData)
- [`useDatabaseList`](#useDatabaseList)
- [`useDatabaseListData`](#useDatabaseListData)
- Cloud Storage
- [`useStorageTask`](#useStorageTask)
- [`useStorageDownloadURL`](#useStorageDownloadURL)
Expand Down Expand Up @@ -187,6 +191,23 @@ _Throws a Promise by default_

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

### `useFirestoreDocData`

Listen to a Firestore Document.

_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 All @@ -204,6 +225,23 @@ _Throws a Promise by default_

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

### `useFirestoreCollectionData`

Listen to a Firestore Collection.

_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 All @@ -221,6 +259,23 @@ _Throws a Promise by default_

[`QueryChange`](https://github.com/firebase/firebase-js-sdk/blob/6b53e0058483c9002d2fe56119f86fc9fb96b56c/packages/rxfire/database/interfaces.ts#L28)

### `useDatabaseObjectData`

Listen to a Realtime Database Object.

_Throws a Promise by default_

#### Parameters

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

#### Returns

`T`

### `useDatabaseList`

Listen to a Realtime Database list.
Expand All @@ -238,6 +293,23 @@ _Throws a Promise by default_

[`QueryChange[]`](https://github.com/firebase/firebase-js-sdk/blob/6b53e0058483c9002d2fe56119f86fc9fb96b56c/packages/rxfire/database/interfaces.ts#L28)

### `useDatabaseListData`

Listen to a Realtime Database list.

_Throws a Promise by default_

#### Parameters

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

#### Returns

`T[]`

### `useStorageTask`

Listen to a Storage UploadTask
Expand Down
61 changes: 57 additions & 4 deletions reactfire/database/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { database } from 'firebase/app';
import { list, object, QueryChange } from 'rxfire/database';
import { ReactFireOptions, useObservable } from '..';
import { list, object, QueryChange, listVal } from 'rxfire/database';
import {
ReactFireOptions,
useObservable,
checkIdField,
checkStartWithValue
} from '..';

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

/**
* Subscribe to a Realtime Database object
Expand All @@ -14,11 +22,45 @@ export function useDatabaseObject<T = unknown>(
): QueryChange | T {
return useObservable(
object(ref),
`RTDB: ${ref.toString()}`,
`RTDB Doc: ${ref.toString()}`,
options ? options.startWithValue : undefined
);
}

// ============================================================================
// TODO: switch to rxfire's objectVal once this PR is merged:
// https://github.com/firebase/firebase-js-sdk/pull/2352

function objectVal<T>(query: database.Query, keyField?: string): Observable<T> {
return object(query).pipe(map(change => changeToData(change, keyField) as T));
}

function changeToData(change: QueryChange, keyField?: string): {} {
const val = change.snapshot.val();

// don't worry about setting IDs if the value is a primitive type
if (typeof val !== 'object') {
return val;
}

return {
...change.snapshot.val(),
...(keyField ? { [keyField]: change.snapshot.key } : null)
};
}
// ============================================================================

export function useDatabaseObjectData<T>(
ref: database.Reference,
options?: ReactFireOptions<T>
): T {
return useObservable(
objectVal(ref, checkIdField(options)),
`RTDB DocData: ${ref.toString()}`,
checkStartWithValue(options)
);
}

// Realtime Database has an undocumented method
// that helps us build a unique ID for the query
// https://github.com/firebase/firebase-js-sdk/blob/aca99669dd8ed096f189578c47a56a8644ac62e6/packages/database/src/api/Query.ts#L601
Expand All @@ -36,11 +78,22 @@ export function useDatabaseList<T = { [key: string]: unknown }>(
ref: database.Reference | database.Query,
options?: ReactFireOptions<T[]>
): QueryChange[] | T[] {
const hash = `RTDB: ${ref.toString()}|${(ref as _QueryWithId).queryIdentifier()}`;
const hash = `RTDB List: ${ref.toString()}|${(ref as _QueryWithId).queryIdentifier()}`;

return useObservable(
list(ref),
hash,
options ? options.startWithValue : undefined
);
}

export function useDatabaseListData<T = { [key: string]: unknown }>(
ref: database.Reference | database.Query,
options?: ReactFireOptions<T[]>
): T[] {
return useObservable(
listVal(ref, checkIdField(options)),
`RTDB ListData: ${ref.toString()}`,
checkStartWithValue(options)
);
}
20 changes: 7 additions & 13 deletions reactfire/firestore/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import {
docData,
fromCollectionRef
} from 'rxfire/firestore';
import { preloadFirestore, ReactFireOptions, useObservable } from '..';
import {
preloadFirestore,
ReactFireOptions,
useObservable,
checkIdField,
checkStartWithValue
} from '..';
import { preloadObservable } from '../useObservable';

// starts a request for a firestore doc.
Expand Down Expand Up @@ -112,15 +118,3 @@ export function useFirestoreCollectionData<T = { [key: string]: unknown }>(
checkStartWithValue(options)
);
}

function checkOptions(options: ReactFireOptions, field: string) {
return options ? options[field] : undefined;
}

function checkStartWithValue(options: ReactFireOptions) {
return checkOptions(options, 'startWithValue');
}

function checkIdField(options: ReactFireOptions) {
return checkOptions(options, 'idField');
}
12 changes: 12 additions & 0 deletions reactfire/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ export interface ReactFireOptions<T = unknown> {
idField?: string;
}

export function checkOptions(options: ReactFireOptions, field: string) {
return options ? options[field] : undefined;
}

export function checkStartWithValue(options: ReactFireOptions) {
return checkOptions(options, 'startWithValue');
}

export function checkIdField(options: ReactFireOptions) {
return checkOptions(options, 'idField');
}

export * from './auth';
export * from './database';
export * from './firebaseApp';
Expand Down
21 changes: 10 additions & 11 deletions sample/src/RealtimeDatabase.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React, { useState } from 'react';
import {
AuthCheck,
SuspenseWithPerf,
useDatabaseList,
useDatabaseObject,
useDatabase
useDatabaseObjectData,
useDatabase,
useDatabaseListData
} from 'reactfire';

const Counter = props => {
Expand All @@ -16,13 +16,12 @@ const Counter = props => {
});
};

const { snapshot } = useDatabaseObject(ref);
const counterValue = snapshot.val();
const count = useDatabaseObjectData(ref);

return (
<>
<button onClick={() => increment(-1)}>-</button>
<span> {counterValue} </span>
<span> {count} </span>
<button onClick={() => increment(1)}>+</button>
</>
);
Expand Down Expand Up @@ -58,7 +57,8 @@ const AnimalEntry = ({ saveAnimal }) => {
const List = props => {
const database = useDatabase();
const ref = database().ref('animals');
const changes = useDatabaseList(ref);
const animals = useDatabaseListData(ref, { idField: 'id' });

const addNewAnimal = commonName => {
const newAnimalRef = ref.push();
return newAnimalRef.set({
Expand All @@ -72,10 +72,9 @@ const List = props => {
<>
<AnimalEntry saveAnimal={addNewAnimal} />
<ul>
{changes.map(({ snapshot }) => (
<li key={snapshot.key}>
{snapshot.val().commonName}{' '}
<button onClick={() => removeAnimal(snapshot.key)}>X</button>
{animals.map(({ commonName, id }) => (
<li key={id}>
{commonName} <button onClick={() => removeAnimal(id)}>X</button>
</li>
))}
</ul>
Expand Down