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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ sample-complex/.cache/**
sample-complex/src/firebase-key.json
reactfire/firestore-debug.log
reactfire/pub/**
pub
pub
yarn-error.log
93 changes: 84 additions & 9 deletions reactfire/firestore/firestore.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import * as firebase from '@firebase/testing';
import {
useFirestoreDoc,
useFirestoreCollection,
FirebaseAppProvider
FirebaseAppProvider,
useFirestoreCollectionData,
useFirestoreDocData,
} from '..';
import { firestore } from 'firebase/app';

describe('Firestore', () => {
let app;
let app: import('firebase').app.App;

beforeAll(async () => {
app = firebase.initializeTestApp({
projectId: '12345',
databaseName: 'my-database',
auth: { uid: 'alice' }
});
}) as import('firebase').app.App;
// TODO(davideast): Wait for rc and analytics to get included in test app
});

afterEach(async () => {
Expand Down Expand Up @@ -49,9 +52,7 @@ describe('Firestore', () => {
await ref.set(mockData);

const ReadFirestoreDoc = () => {
const doc = useFirestoreDoc(
(ref as unknown) as firestore.DocumentReference
);
const doc = useFirestoreDoc(ref);

return (
<h1 data-testid="readSuccess">
Expand All @@ -72,6 +73,41 @@ describe('Firestore', () => {
expect(getByTestId('readSuccess')).toContainHTML(mockData.a);
});
});

describe('useFirestoreDocData', () => {
it('can get a Firestore document [TEST REQUIRES EMULATOR]', async () => {
const mockData = { a: 'hello' };

const ref = app
.firestore()
.collection('testDoc')
// 'readSuccess' is set to the data-testid={data.id} attribute
.doc('readSuccess');

await ref.set(mockData);

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

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

await waitForElement(() => getByTestId('readSuccess'));

expect(getByTestId('readSuccess')).toContainHTML(mockData.a);
});
});

// THIS TEST CAUSES A REACT `act` WARNING
// IT WILL BE FIXED IN REACT 16.9
Expand All @@ -87,9 +123,7 @@ describe('Firestore', () => {
await ref.add(mockData2);

const ReadFirestoreCollection = () => {
const collection = useFirestoreCollection(
(ref as unknown) as firestore.CollectionReference
);
const collection = useFirestoreCollection(ref);

return (
<ul data-testid="readSuccess">
Expand All @@ -114,4 +148,45 @@ describe('Firestore', () => {
expect(getAllByTestId('listItem').length).toEqual(2);
});
});

// THIS TEST CAUSES A REACT `act` WARNING
// IT WILL BE FIXED IN REACT 16.9
// More info here: https://github.com/testing-library/react-testing-library/issues/281
describe('useFirestoreCollectionData', () => {
it('can get a Firestore collection [TEST REQUIRES EMULATOR]', async () => {
const mockData1 = { a: 'hello' };
const mockData2 = { a: 'goodbye' };

const ref = app.firestore().collection('testCollection');

await ref.add(mockData1);
await ref.add(mockData2);

const ReadFirestoreCollection = () => {
const list = useFirestoreCollectionData<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);
});
});

});
48 changes: 47 additions & 1 deletion reactfire/firestore/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { firestore } from 'firebase/app';
import { doc, fromCollectionRef } from 'rxfire/firestore';
import { doc, collectionData, fromCollectionRef, docData } from 'rxfire/firestore';
import { ReactFireOptions, useObservable } from '..';

/**
Expand All @@ -19,6 +19,23 @@ export function useFirestoreDoc<T = unknown>(
);
}

/**
* Suscribe to Firestore Document changes
*
* @param ref - Reference to the document you want to listen to
* @param options
*/
export function useFirestoreDocData<T = unknown>(
ref: firestore.DocumentReference,
options?: ReactFireOptions<T>
): T {
return useObservable(
docData(ref, checkIdField(options)),
ref.path,
checkStartWithValue(options)
);
}

/**
* Subscribe to a Firestore collection
*
Expand All @@ -35,3 +52,32 @@ export function useFirestoreCollection<T = { [key: string]: unknown }>(
options ? options.startWithValue : undefined
);
}

/**
* Subscribe to a Firestore collection and unwrap the snapshot.
*
* @param ref - Reference to the collection you want to listen to
* @param options
*/
export function useFirestoreCollectionData<T = { [key: string]: unknown }>(
ref: firestore.CollectionReference,
options?: ReactFireOptions<T[]>
): T[] {
return useObservable(
collectionData(ref, checkIdField(options)),
ref.path,
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');
}
3 changes: 2 additions & 1 deletion reactfire/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface ReactFireOptions<T = unknown> {
startWithValue: T;
startWithValue?: T;
idField?: string;
}

export * from './auth';
Expand Down
6 changes: 2 additions & 4 deletions reactfire/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
"rxjs": "^6.4.0"
},
"devDependencies": {
"firebase": "^7.0.0",
"react": "^16.9.0",
"@babel/core": "^7.4.3",
"@babel/preset-env": "^7.4.3",
"@babel/preset-react": "^7.0.0",
Expand All @@ -46,7 +44,7 @@
"jest": "~24.7.1",
"jest-dom": "^3.1.3",
"typescript": "^3.4.5",
"firebase-functions-test": "^0.1.6"
"react-test-renderer": "^16.9.0",
"firebase-functions-test": "^0.1.6",
"react-test-renderer": "^16.9.0"
}
}
23 changes: 11 additions & 12 deletions sample-simple/src/Firestore.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'firebase/auth';
import 'firebase/firestore';
import '@firebase/performance';
import 'firebase/performance';
import React, { useState } from 'react';
import {
AuthCheck,
SuspenseWithPerf,
useFirestoreCollection,
useFirestoreDoc,
useFirestoreCollectionData,
useFirestoreDocData,
useFirebaseApp
} from 'reactfire';

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

const snapshot = useFirestoreDoc(ref);
const counterValue = snapshot.data().value;
const { value } = useFirestoreDocData(ref);

return (
<>
<button onClick={() => increment(-1)}>-</button>
<span> {counterValue} </span>
<span> {value} </span>
<button onClick={() => increment(1)}>+</button>
</>
);
Expand Down Expand Up @@ -62,7 +61,7 @@ const AnimalEntry = ({ saveAnimal }) => {
const List = props => {
const firebaseApp = useFirebaseApp();
const ref = firebaseApp.firestore().collection('animals');
const snapShot = useFirestoreCollection(ref);
const animals = useFirestoreCollectionData(ref, { idField: 'id' });

const addNewAnimal = commonName =>
ref.add({
Expand All @@ -75,11 +74,11 @@ const List = props => {
<>
<AnimalEntry saveAnimal={addNewAnimal} />
<ul>
{snapShot.docs.map(snap => (
<li key={snap.id}>
{snap.get('commonName')}{' '}
<button onClick={() => removeAnimal(snap.id)}>X</button>
</li>
{animals.map(animal => (
<li key={animal.id}>
{animal.commonName}{' '}
<button onClick={() => removeAnimal(animal.id)}>X</button>
</li>
))}
</ul>
</>
Expand Down
Loading