From 1aeb39385088f8d84d83013fa474cf8e15444f7b Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Fri, 31 Jan 2020 11:10:41 -0800 Subject: [PATCH 01/18] add sdk init to docs --- docs/use.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/use.md b/docs/use.md index b66af46c..dc3c0e21 100644 --- a/docs/use.md +++ b/docs/use.md @@ -187,6 +187,16 @@ The [render-as-you-fetch pattern](https://reactjs.org/docs/concurrent-mode-suspe Just as the SDK hooks like `useFirestore` can automatically fetch an SDK, you can call `preloadFirestore` (or `preloadAuth`, etc) to start loading an SDK without suspending. +### Initialize an SDK + +A few Firestore SDKs need to be initialized (`firebase().performance().initialize()`, `firebase.remoteConfig().fetchAndActivate()`), or need to have settings set before any other calls are made (`firebase.firestore().enablePersistence()`). This can be done by using an extra argument in the preload method: + +```jsx +preloadFirestore(firestore => { + return firestore().enablePersistence(); +}); +``` + ### Preload Data Many ReactFire hooks have corresponding preload functions. For example, you can call `preloadFirestoreDocData` to preload data if a component later calls `useFirestoreDocData`. From c43d565115992bdabe17168f0a00dce9e5f9c43e Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 10 Feb 2020 10:45:57 -0500 Subject: [PATCH 02/18] add profile page example --- docs/use.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/docs/use.md b/docs/use.md index dc3c0e21..3a613ed2 100644 --- a/docs/use.md +++ b/docs/use.md @@ -11,6 +11,8 @@ - [Preloading](#preloading) - [Preload an SDK](#preload-an-sdk) - [Preload Data](#preload-data) +- [Combining ReactFire Hooks](#combining-reactfire-hooks) + - [Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card](#todo) ## Access your `firebase` object from any component @@ -200,3 +202,73 @@ preloadFirestore(firestore => { ### Preload Data Many ReactFire hooks have corresponding preload functions. For example, you can call `preloadFirestoreDocData` to preload data if a component later calls `useFirestoreDocData`. + +### Combining ReactFire Hooks + +#### Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card + +```jsx +import { + AuthCheck, + StorageImage, + useFirestoreDocData, + useUser, + useAuth, + useFirestore +} from 'reactfire'; + +const DEFAULT_IMAGE_PATH = 'userPhotos/default.jpg'; + +function ProfileCard() { + // get the current user. + // this is safe because we've wrapped this component in an `AuthCheck` component. + const user = useUser(); + + // read the user details from Firestore based on the current user's ID + const userDetailsRef = useFirestore() + .collection('users') + .doc(user.uid); + let { commonName, favoriteAnimal, profileImagePath } = useFirestoreDocData( + userDetailsRef + ); + + // defend against null field(s) + profileImagePath = profileImagePath || DEFAULT_IMAGE_PATH; + + return ( +
+

{commonName}

+ {/* + `StorageImage` converts a Cloud Storage path into a download URL and then renders an image + */} + + Your favorite animal is the {favoriteAnimal} +
+ ); +} + +function LogInForm() { + const auth = useAuth(); + + const signIn = () => { + auth().signInWithEmailAndPassword(email, password); + }; + + return ; +} + +function ProfilePage() { + return ( + {/* + Render a spinner until components are ready + */} + }> + {/* + Render `ProfileCard` only if a user is signed in. + Otherwise, render `LoginForm` + */} + }>{ProfileCard} + + ); +} +``` From e7eaa7dd23541c813035ddcf4cdd7cbb6e53cbcd Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 10 Feb 2020 11:08:02 -0500 Subject: [PATCH 03/18] add Analytics example --- docs/use.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/use.md b/docs/use.md index 3a613ed2..2400cbbe 100644 --- a/docs/use.md +++ b/docs/use.md @@ -11,6 +11,7 @@ - [Preloading](#preloading) - [Preload an SDK](#preload-an-sdk) - [Preload Data](#preload-data) +- [Log Page Views with React Router](#todo) - [Combining ReactFire Hooks](#combining-reactfire-hooks) - [Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card](#todo) @@ -272,3 +273,36 @@ function ProfilePage() { ); } ``` + +### Log Page Views to Google Analytics for Firebase with React Router + +```jsx +import { useAnalytics } from 'reactfire'; +import { Router, Route, Switch } from 'react-router'; + +function MyPageViewLogger({ location }) { + const analytics = useAnalytics(); + + // By passing `location.pathname` to the second argument of `useEffect`, + // we only log on first render and when the `pathname` changes + useEffect(() => { + analytics.logEvent('page-view', location.pathname); + }, [location.pathname]); + + return null; +} + +function App() { + const analytics = useAnalytics(); + + return ( + + + } /> + } /> + + + + ); +} +``` From c7ba9f3a09d5f9467d0f8305b75d5712f1ccd81e Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 09:47:20 -0800 Subject: [PATCH 04/18] address review comments --- docs/use.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/use.md b/docs/use.md index 2400cbbe..7b2ca00b 100644 --- a/docs/use.md +++ b/docs/use.md @@ -192,10 +192,10 @@ Just as the SDK hooks like `useFirestore` can automatically fetch an SDK, you ca ### Initialize an SDK -A few Firestore SDKs need to be initialized (`firebase().performance().initialize()`, `firebase.remoteConfig().fetchAndActivate()`), or need to have settings set before any other calls are made (`firebase.firestore().enablePersistence()`). This can be done by using an extra argument in the preload method: +Some Firestore SDKs need to be initialized (`firebase.remoteConfig().fetchAndActivate()`), or need to have settings set before any other calls are made (`firebase.firestore().enablePersistence()`). This can be done by using an extra argument in the preload method: ```jsx -preloadFirestore(firestore => { +preloadFirestore(firebaseApp, firestore => { return firestore().enablePersistence(); }); ``` @@ -252,7 +252,7 @@ function LogInForm() { const auth = useAuth(); const signIn = () => { - auth().signInWithEmailAndPassword(email, password); + auth.signInWithEmailAndPassword(email, password); }; return ; @@ -286,7 +286,7 @@ function MyPageViewLogger({ location }) { // By passing `location.pathname` to the second argument of `useEffect`, // we only log on first render and when the `pathname` changes useEffect(() => { - analytics.logEvent('page-view', location.pathname); + analytics.logEvent('page-view', { path_name: location.pathname }); }, [location.pathname]); return null; From 077e0fae1cc1fa9e9253e86d3fc64d807edb7380 Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 09:57:54 -0800 Subject: [PATCH 05/18] update root description --- README.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 65211742..663a60fe 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,20 @@ Hooks, Context Providers, and Components that make it easy to interact with Firebase. -> If you're looking for docs for the _deprecated_ ReactFire v1 (the one that -> uses mixins), click -> [here](https://github.com/FirebaseExtended/reactfire/tree/v1.0.0) - -**Status: Alpha**. ReactFire is meant for React Concurrent Mode, which is only +⚠️ **Status: Alpha**. ReactFire is meant for React Concurrent Mode, which is only available in [experimental React builds](https://reactjs.org/docs/concurrent-mode-adoption.html#installation). ## What is ReactFire? -- **Easy realtime updates for your function components** - Reactfire's hooks, - like `useFirestoreCollection` and `useUser`, let you easily subscribe to - events, and automatically unsubscribe when your component unmounts. +- **Easy realtime updates for your function components** - Hooks + like `useUser`and `useFirestoreCollection` let you easily subscribe to + auth state, realtime data, and all other Firebase SDK events. Plus, they automatically unsubscribe when your component unmounts. - **Loading states handled by ``** - Reactfire's hooks throw promises that Suspense can catch. No more `isLoaded ?...` - let React - [handle it for you](https://reactjs.org/blog/2018/11/27/react-16-roadmap.html#react-166-shipped-the-one-with-suspense-for-code-splitting). -- **Dead-simple Real User Monitoring (RUM)** - Easily enable Firebase - Performance Monitoring's - [automatic traces](https://firebase.google.com/docs/perf-mon/automatic-web), - and instrument your Suspenseful loads with Reactfire's `` - component + [handle it for you](https://reactjs.org/docs/concurrent-mode-suspense.html). +- **Faster initial page load times** - Load only the code you need, when you need it, with `useFirestore`, `useAuth`, `useRemoteConfig`, and more. +- **Convenient components for common use cases** - Only want to render a component if a user is signed in? Wrap it in ``. Need to automatically instrument your `Suspense` load times with [RUM](https://firebase.google.com/docs/perf-mon)? Use ``. ## Install @@ -115,3 +108,9 @@ render(, document.getElementById('root')); 1. `cd` into the _reactfire/reactfire_ directory 1. run `yarn test` + +--- + +> If you're looking for docs for the _deprecated_ ReactFire v1 (the one that +> uses mixins), click +> [here](https://github.com/FirebaseExtended/reactfire/tree/v1.0.0) From fbc47c24618ba467c4cf680535c3fee624f8c2c2 Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 10:11:23 -0800 Subject: [PATCH 06/18] enable concurrent mode in initial sample --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e9253e3e..0f1ed6e4 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,7 @@ Check out the ```jsx import React, { Component } from 'react'; -import { render } from 'react-dom'; -import './style.css'; +import { createRoot } from 'react-dom'; import { FirebaseAppProvider, useFirestoreDocData, @@ -78,7 +77,9 @@ function App() { ); } -render(, document.getElementById('root')); +// Enable Concurrent Mode +// https://reactjs.org/docs/concurrent-mode-adoption.html#enabling-concurrent-mode +createRoot(document.getElementById('root')).render(); ``` ## Learn More From 2452d8a9006fb9125e12fb35145b298161e7dc8d Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 10:11:32 -0800 Subject: [PATCH 07/18] update quickstart --- docs/quickstart.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index ad8ac64d..67495737 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -6,7 +6,7 @@ We'll build a web app that displays, in _real time_, the tastiness of a burrito. To see the completed app, check out [this StackBlitz workspace](https://stackblitz.com/edit/reactfire-sample). -## 1. In a terminal, create a fresh React app and `cd` into its directory. +## 1. In a terminal, create a fresh React app and `cd` into its directory > Prerequisite: make sure you have [Node.js](https://nodejs.org/en/) installed. @@ -53,7 +53,6 @@ npm install --save firebase reactfire ```js //... import { FirebaseAppProvider } from 'reactfire'; - import 'firebase/performance'; //... ``` @@ -64,11 +63,10 @@ npm install --save firebase reactfire const firebaseConfig = { /* add your config object from Firebase console */ }; - ReactDOM.render( - + ReactDOM.createRoot(document.getElementById('root')).render( + - , - document.getElementById('root') + ); //... ``` @@ -88,11 +86,8 @@ npm install --save firebase reactfire ```jsx //... function Burrito() { - // lazy load the Firestore SDK - const firestore = useFirestore(); - - // create a document reference - const burritoRef = firestore() + // lazy load the Firestore SDK and create a document reference + const burritoRef = useFirestore() .collection('tryreactfire') .doc('burrito'); @@ -118,6 +113,11 @@ Replace the `App` function with the following: function App() { return (
+ {/* + SuspenseWithPerf behaves the same as Suspense, + but also automatically measures load times with the User Timing API + and reports it to Firebase Performance Monitoring + */} Date: Mon, 24 Feb 2020 10:12:58 -0800 Subject: [PATCH 08/18] automatically fork on stackblitz --- README.md | 2 +- docs/quickstart.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f1ed6e4..1538eede 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ yarn add reactfire ## Example use Check out the -[live version on StackBlitz](https://stackblitz.com/edit/reactfire-sample)! +[live version on StackBlitz](https://stackblitz.com/fork/reactfire-sample)! ```jsx import React, { Component } from 'react'; diff --git a/docs/quickstart.md b/docs/quickstart.md index 67495737..888946d8 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -4,7 +4,7 @@ We'll build a web app that displays, in _real time_, the tastiness of a burrito. It will listen to **Cloud Firestore** for its data, and we'll configure **Firebase Performance Monitoring** so we can get some perf stats. -To see the completed app, check out [this StackBlitz workspace](https://stackblitz.com/edit/reactfire-sample). +To see the completed app, check out [this StackBlitz workspace](https://stackblitz.com/fork/reactfire-sample). ## 1. In a terminal, create a fresh React app and `cd` into its directory From 2e2da81bd33cc3697d5f7c6f276e74d121253e7b Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 10:40:12 -0800 Subject: [PATCH 09/18] fix readme sample --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1538eede..ac797d42 100644 --- a/README.md +++ b/README.md @@ -39,15 +39,17 @@ import { createRoot } from 'react-dom'; import { FirebaseAppProvider, useFirestoreDocData, + useFirestore, SuspenseWithPerf } from 'reactfire'; const firebaseConfig = { - /* add your config object from the Firebase console */ + /* Add your config from the Firebase Console */ }; function Burrito() { - // lazy load the Firestore SDK and create a ref + // lazy load the Firestore SDK + // and create a ref const burritoRef = useFirestore() .collection('tryreactfire') .doc('burrito'); @@ -57,18 +59,15 @@ function Burrito() { // and then streams live updates const burrito = useFirestoreDocData(burritoRef); - // get the value from the doc - const isYummy = burrito.yummy; - - return

The burrito is {isYummy ? 'good' : 'bad'}!

; + return

The burrito is {burrito.yummy ? 'good' : 'bad'}!

; } function App() { return ( - +

🌯

loading burrito status...

} traceId={'load-burrito-status'} > From cafd764589d6ba7cea3fe4ae4e7d9b1c879c169b Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 10:49:02 -0800 Subject: [PATCH 10/18] remove contributing in favor of contributing.md --- README.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/README.md b/README.md index ac797d42..041d2678 100644 --- a/README.md +++ b/README.md @@ -87,25 +87,6 @@ createRoot(document.getElementById('root')).render(); - [**Common Use Cases**](./docs/use.md) - [**API Reference**](./docs/reference.md) -## Contributing - -### For development - -1. [Clone](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) - this repository (or a - [fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo#propose-changes-to-someone-elses-project)) -1. At the project root, install all modules by running `yarn install`. -1. `cd` into the _reactfire_ directory. Run `yarn` and `yarn watch`. -1. In a new terminal, `cd` into the _reactfire/sample_ directory. run `yarn` and - `yarn start`. -1. Head over to https://localhost:3000 to see the running sample! If you edit - the reactfire source, the sample will reload. - -### Testing - -1. `cd` into the _reactfire/reactfire_ directory -1. run `yarn test` - --- > If you're looking for docs for the _deprecated_ ReactFire v1 (the one that From defb80db7fa418147fd3b897fb158c657d41d314 Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 10:55:26 -0800 Subject: [PATCH 11/18] move docs links higher up --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 041d2678..9f03ecfd 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ npm install --save reactfire yarn add reactfire ``` +- [**Quickstart**](./docs/quickstart.md) +- [**Common Use Cases**](./docs/use.md) +- [**API Reference**](./docs/reference.md) + ## Example use Check out the @@ -81,12 +85,6 @@ function App() { createRoot(document.getElementById('root')).render(); ``` -## Learn More - -- [**Quickstart**](./docs/quickstart.md) -- [**Common Use Cases**](./docs/use.md) -- [**API Reference**](./docs/reference.md) - --- > If you're looking for docs for the _deprecated_ ReactFire v1 (the one that From 2cde62cdbe74ee6e0f5399c986547465d71a083b Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 12:52:55 -0800 Subject: [PATCH 12/18] document useObservable and preloadFirestoreDoc --- docs/reference.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/reference.md b/docs/reference.md index 495375e3..556c784a 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -35,6 +35,7 @@ - Cloud Storage - [`useStorageTask`](#useStorageTask) - [`useStorageDownloadURL`](#useStorageDownloadURL) + - [`useObservable`](#useObservable) - [ReactFireOptions](#ReactFireOptions) - [Components](#Components) @@ -56,6 +57,8 @@ - [`preloadPerformance`](#preloadPerformance) - [`preloadRemoteConfig`](#preloadRemoteConfig) - [`preloadStorage`](#preloadStorage) + - Data + - [`preloadFirestoreDoc`](#preloadFirestoreDoc) ## Hooks @@ -381,6 +384,21 @@ _Throws a Promise by default_ `string` +### `useObservable` + +_Throws a Promise by default_ + +| Parameter | Type | Description | +| ------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| source | `Observable` | The observable whose values you want to subscribe to | +| observableId | `string` | A unique id. If this id matches an observable already in the cache, `useObservable` will reuse that observable instead of the one passed in. | +| startWithValue _?_ | `T` | A value to emit first (if you don't want `useObservable` to throw a Promise) | +| deps | React.DependencyList | A list of values that, when changed, should cause `useObservable` to re-subscribe to its observable | + +##### Returns + +`T` + ## ReactFireOptions | Property | Type | @@ -578,3 +596,20 @@ Start importing the `firebase/storage` package. #### Returns `Promise<`[`firebase.storage`](https://firebase.google.com/docs/reference/js/firebase.storage)`>` + +### `preloadFirestoreDoc` + +⚠️ experimental + +Starts subscribing to a Firestore document in the background. Cleans itself up after 30 seconds if `useFirestoreDoc` calls are made. + +#### Parameters + +| Parameter | Type | Description | +| ----------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| refProvider | `(firestore: firebase.firestore.Firestore) => firestore.DocumentReference` | A function that is called when the firestore SDK is ready and generates a DocumentReference to read | +| firebaseApp | `firebase.app.App` | The firebase app | + +#### Returns + +`Promise>` From b81727da1f1419f543af09f2070888cd01db7c6d Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 12:53:17 -0800 Subject: [PATCH 13/18] update usage docs --- docs/use.md | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/docs/use.md b/docs/use.md index 7b2ca00b..40a19bbb 100644 --- a/docs/use.md +++ b/docs/use.md @@ -1,23 +1,23 @@ -# Common Patterns +# Using ReactFire -- [Access your `firebase` object from any component](#access-your-firebase-object-from-any-component) +- [Access your `firebase` app from any component](#access-your-firebase-object-from-any-component) +- [Access the current user](#access-the-current-user) + - [Decide what to render based on a user's auth state](#decide-what-to-render-based-on-a-users-auth-state) +- [Log Page Views with React Router](#todo) +- [Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card](#todo) - [Manage Loading States](#manage-loading-states) - [Default: `Suspense`](#default-suspense) - [Bonus: `SuspenseWithPerf`](#bonus-suspensewithperf) - [Provide an initial value](#provide-an-initial-value) -- [Access the current user](#access-the-current-user) - - [Decide what to render based on a user's auth state](#decide-what-to-render-based-on-a-users-auth-state) - [Lazy Load the Firebase SDKs](#lazy-load-the-Firebase-SDKs) - [Preloading](#preloading) - [Preload an SDK](#preload-an-sdk) - [Preload Data](#preload-data) -- [Log Page Views with React Router](#todo) -- [Combining ReactFire Hooks](#combining-reactfire-hooks) - - [Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card](#todo) +- [Advanced: Using RxJS observables to combine multiple data sources](#todo) ## Access your `firebase` object from any component -Since reactfire uses React's Context API, any component under a `FirebaseAppProvider` can use `useFirebaseApp()` to get your initialized app. +Since reactfire uses React's Context API, any component under a `FirebaseAppProvider` can use `useFirebaseApp()` to get your initialized app. Plus, all ReactFire hooks will automatically check context to see if a firebase app is available. ```jsx // ** INDEX.JS ** @@ -34,8 +34,8 @@ render( // ** MYCOMPONENT.JS ** function MyComponent(props) { - const firestore = useFirestore(); - const documentReference = firestore() + // useFirestore will get the firebase app from Context! + const documentReference = useFirestore() .collection('burritos') .doc('vegetarian'); @@ -98,7 +98,7 @@ function FoodRatings() { } ``` -### Provide an initial value +### Don't want Suspense? Provide an initial value What if we don't want to use Suspense, or we're server rendering and we know what the initial value should be? In that case we can provide an initial value to any Reactfire hook: @@ -182,13 +182,13 @@ export function MyComponent(props) { } ``` -## Preloading +## The _render-as-you-fetch_ pattern -The [render-as-you-fetch pattern](https://reactjs.org/docs/concurrent-mode-suspense.html#approach-3-render-as-you-fetch-using-suspense) encourages kicking off requests as early as possible instead of waiting until a component renders. ReactFire supports this behavior +The [React docs](https://reactjs.org/docs/concurrent-mode-suspense.html#approach-3-render-as-you-fetch-using-suspense) recommend kicking off reads as early as possible in order to reduce perceived load times. ReactFire offers a number of `preload` methods to help you do this. ### Preload an SDK -Just as the SDK hooks like `useFirestore` can automatically fetch an SDK, you can call `preloadFirestore` (or `preloadAuth`, etc) to start loading an SDK without suspending. +Call `preloadFirestore` (or `preloadAuth`, `preloadRemoteConfig`, etc) to start fetching a Firebase library in the background. Later, when you call `useFirestore` in a component, the `useFirestore` hook may not need to suspend if the preload has already completed. ### Initialize an SDK @@ -202,11 +202,9 @@ preloadFirestore(firebaseApp, firestore => { ### Preload Data -Many ReactFire hooks have corresponding preload functions. For example, you can call `preloadFirestoreDocData` to preload data if a component later calls `useFirestoreDocData`. - -### Combining ReactFire Hooks +ReactFire's data fetching hooks don't fully support preloading yet. The experimental `preloadFirestoreDoc` function allows you to subscribe to a Firestore document if you know you call `useFirestoreDoc` somewhere farther down the component tree. -#### Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card +## Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card ```jsx import { @@ -274,7 +272,7 @@ function ProfilePage() { } ``` -### Log Page Views to Google Analytics for Firebase with React Router +## Log Page Views to Google Analytics for Firebase with React Router ```jsx import { useAnalytics } from 'reactfire'; @@ -306,3 +304,7 @@ function App() { ); } ``` + +## Advanced: Using RxJS observables to combine multiple data sources + +All ReactFire hooks are powered by [`useObservable`](./reference.md#useObservable). By calling `useObservable` directly, you can subscribe to any observable in the same manner as the built-in ReactFire hooks. If you use [RxFire](https://github.com/firebase/firebase-js-sdk/tree/master/packages/rxfire#rxfire) and `useObservable` together, you can accomplish more advanced read patterns (like [OR queries in Firestore](https://stackoverflow.com/a/53497072/4816918)!). From 1cb696f4a3387e299575c963159a906c974af791 Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 12:58:31 -0800 Subject: [PATCH 14/18] add useTransition docs --- docs/use.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/use.md b/docs/use.md index 40a19bbb..ffaf1e06 100644 --- a/docs/use.md +++ b/docs/use.md @@ -133,6 +133,12 @@ function FoodRatings() { } ``` +### Solve `Warning: App triggered a user-blocking update that suspended. with useTransition` + +This warning can be solved with React's `useTransition` hook. Check out the sample code's Firestore example to see how to use this with ReactFire: + +https://github.com/FirebaseExtended/reactfire/blob/c67dfa755431c15034f0c713b9df3864fb762c06/sample/src/Firestore.js#L87-L121 + ## Access the current user The `useUser()` hook returns the currently signed-in [user](https://firebase.google.com/docs/reference/js/firebase.User). Like the other Reactfire Hooks, you need to wrap it in `Suspense` or provide a `startWithValue`. From d86dc2a9f6ced698e2a8cf80e4f63b0296a1e742 Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 13:15:04 -0800 Subject: [PATCH 15/18] fix anchor links --- docs/use.md | 270 ++++++++++++++++++++++++++-------------------------- 1 file changed, 135 insertions(+), 135 deletions(-) diff --git a/docs/use.md b/docs/use.md index ffaf1e06..9cdaad52 100644 --- a/docs/use.md +++ b/docs/use.md @@ -1,21 +1,21 @@ # Using ReactFire -- [Access your `firebase` app from any component](#access-your-firebase-object-from-any-component) +- [Access your `firebase` app from any component](#access-your-firebase-app-from-any-component) - [Access the current user](#access-the-current-user) - [Decide what to render based on a user's auth state](#decide-what-to-render-based-on-a-users-auth-state) -- [Log Page Views with React Router](#todo) -- [Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card](#todo) +- [Log Page Views with React Router](#log-page-views-to-google-analytics-for-firebase-with-react-router) +- [Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card](#combine-auth-firestore-and-cloud-storage-to-show-a-user-profile-card) - [Manage Loading States](#manage-loading-states) - [Default: `Suspense`](#default-suspense) - [Bonus: `SuspenseWithPerf`](#bonus-suspensewithperf) - - [Provide an initial value](#provide-an-initial-value) + - [Provide an initial value](#dont-want-suspense-provide-an-initial-value) - [Lazy Load the Firebase SDKs](#lazy-load-the-Firebase-SDKs) -- [Preloading](#preloading) +- [The _render-as-you-fetch_ pattern](#the-render-as-you-fetch-pattern) - [Preload an SDK](#preload-an-sdk) - [Preload Data](#preload-data) -- [Advanced: Using RxJS observables to combine multiple data sources](#todo) +- [Advanced: Using RxJS observables to combine multiple data sources](#advanced-using-rxjs-observables-to-combine-multiple-data-sources) -## Access your `firebase` object from any component +## Access your `firebase` app from any component Since reactfire uses React's Context API, any component under a `FirebaseAppProvider` can use `useFirebaseApp()` to get your initialized app. Plus, all ReactFire hooks will automatically check context to see if a firebase app is available. @@ -43,6 +43,134 @@ function MyComponent(props) { } ``` +## Access the current user + +The `useUser()` hook returns the currently signed-in [user](https://firebase.google.com/docs/reference/js/firebase.User). Like the other Reactfire Hooks, you need to wrap it in `Suspense` or provide a `startWithValue`. + +```jsx +function HomePage(props) { + // no need to use useFirebaseApp - useUser calls it under the hood + const user = useUser(); + + return

Welcome Back {user.displayName}!

; +} +``` + +Note: `useUser` will also automatically lazily import the `firebase/auth` SDK if it has not been imported already. + +### Decide what to render based on a user's auth state + +The `AuthCheck` component makes it easy to hide/show UI elements based on a user's auth state. It will render its children if a user is signed in, but if they are not signed in, it renders its `fallback` prop: + +```jsx +render( + }> + + +); +``` + +## Log Page Views to Google Analytics for Firebase with React Router + +```jsx +import { useAnalytics } from 'reactfire'; +import { Router, Route, Switch } from 'react-router'; + +function MyPageViewLogger({ location }) { + const analytics = useAnalytics(); + + // By passing `location.pathname` to the second argument of `useEffect`, + // we only log on first render and when the `pathname` changes + useEffect(() => { + analytics.logEvent('page-view', { path_name: location.pathname }); + }, [location.pathname]); + + return null; +} + +function App() { + const analytics = useAnalytics(); + + return ( + + + } /> + } /> + + + + ); +} +``` + +## Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card + +```jsx +import { + AuthCheck, + StorageImage, + useFirestoreDocData, + useUser, + useAuth, + useFirestore +} from 'reactfire'; + +const DEFAULT_IMAGE_PATH = 'userPhotos/default.jpg'; + +function ProfileCard() { + // get the current user. + // this is safe because we've wrapped this component in an `AuthCheck` component. + const user = useUser(); + + // read the user details from Firestore based on the current user's ID + const userDetailsRef = useFirestore() + .collection('users') + .doc(user.uid); + let { commonName, favoriteAnimal, profileImagePath } = useFirestoreDocData( + userDetailsRef + ); + + // defend against null field(s) + profileImagePath = profileImagePath || DEFAULT_IMAGE_PATH; + + return ( +
+

{commonName}

+ {/* + `StorageImage` converts a Cloud Storage path into a download URL and then renders an image + */} + + Your favorite animal is the {favoriteAnimal} +
+ ); +} + +function LogInForm() { + const auth = useAuth(); + + const signIn = () => { + auth.signInWithEmailAndPassword(email, password); + }; + + return ; +} + +function ProfilePage() { + return ( + {/* + Render a spinner until components are ready + */} + }> + {/* + Render `ProfileCard` only if a user is signed in. + Otherwise, render `LoginForm` + */} + }>{ProfileCard} + + ); +} +``` + ## Manage Loading States Reactfire is designed to integrate with React's Suspense API, but also supports use cases where Suspense isn't needed or wanted. @@ -139,33 +267,6 @@ This warning can be solved with React's `useTransition` hook. Check out the samp https://github.com/FirebaseExtended/reactfire/blob/c67dfa755431c15034f0c713b9df3864fb762c06/sample/src/Firestore.js#L87-L121 -## Access the current user - -The `useUser()` hook returns the currently signed-in [user](https://firebase.google.com/docs/reference/js/firebase.User). Like the other Reactfire Hooks, you need to wrap it in `Suspense` or provide a `startWithValue`. - -```jsx -function HomePage(props) { - // no need to use useFirebaseApp - useUser calls it under the hood - const user = useUser(); - - return

Welcome Back {user.displayName}!

; -} -``` - -Note: `useUser` will also automatically lazily import the `firebase/auth` SDK if it has not been imported already. - -### Decide what to render based on a user's auth state - -The `AuthCheck` component makes it easy to hide/show UI elements based on a user's auth state. It will render its children if a user is signed in, but if they are not signed in, it renders its `fallback` prop: - -```jsx -render( - }> - - -); -``` - ## Lazy Load the Firebase SDKs Including the Firebase SDKs in your main JS bundle (by using `import 'firebase/firestore'`, for example) will increase your bundle size. To get around this, you can lazy load the Firebase SDK with ReactFire. As long as a component has a parent that is a `FirebaseAppProvider`, you can use an SDK hook (`useFirestore`, `useDatabase`, `useAuth`, `useStorage`) like so: @@ -210,107 +311,6 @@ preloadFirestore(firebaseApp, firestore => { ReactFire's data fetching hooks don't fully support preloading yet. The experimental `preloadFirestoreDoc` function allows you to subscribe to a Firestore document if you know you call `useFirestoreDoc` somewhere farther down the component tree. -## Combine Auth, Firestore, and Cloud Storage to Show a User Profile Card - -```jsx -import { - AuthCheck, - StorageImage, - useFirestoreDocData, - useUser, - useAuth, - useFirestore -} from 'reactfire'; - -const DEFAULT_IMAGE_PATH = 'userPhotos/default.jpg'; - -function ProfileCard() { - // get the current user. - // this is safe because we've wrapped this component in an `AuthCheck` component. - const user = useUser(); - - // read the user details from Firestore based on the current user's ID - const userDetailsRef = useFirestore() - .collection('users') - .doc(user.uid); - let { commonName, favoriteAnimal, profileImagePath } = useFirestoreDocData( - userDetailsRef - ); - - // defend against null field(s) - profileImagePath = profileImagePath || DEFAULT_IMAGE_PATH; - - return ( -
-

{commonName}

- {/* - `StorageImage` converts a Cloud Storage path into a download URL and then renders an image - */} - - Your favorite animal is the {favoriteAnimal} -
- ); -} - -function LogInForm() { - const auth = useAuth(); - - const signIn = () => { - auth.signInWithEmailAndPassword(email, password); - }; - - return ; -} - -function ProfilePage() { - return ( - {/* - Render a spinner until components are ready - */} - }> - {/* - Render `ProfileCard` only if a user is signed in. - Otherwise, render `LoginForm` - */} - }>{ProfileCard} - - ); -} -``` - -## Log Page Views to Google Analytics for Firebase with React Router - -```jsx -import { useAnalytics } from 'reactfire'; -import { Router, Route, Switch } from 'react-router'; - -function MyPageViewLogger({ location }) { - const analytics = useAnalytics(); - - // By passing `location.pathname` to the second argument of `useEffect`, - // we only log on first render and when the `pathname` changes - useEffect(() => { - analytics.logEvent('page-view', { path_name: location.pathname }); - }, [location.pathname]); - - return null; -} - -function App() { - const analytics = useAnalytics(); - - return ( - - - } /> - } /> - - - - ); -} -``` - ## Advanced: Using RxJS observables to combine multiple data sources All ReactFire hooks are powered by [`useObservable`](./reference.md#useObservable). By calling `useObservable` directly, you can subscribe to any observable in the same manner as the built-in ReactFire hooks. If you use [RxFire](https://github.com/firebase/firebase-js-sdk/tree/master/packages/rxfire#rxfire) and `useObservable` together, you can accomplish more advanced read patterns (like [OR queries in Firestore](https://stackoverflow.com/a/53497072/4816918)!). From fe0be7713319ebd582159cd96de58afa9026924e Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Mon, 24 Feb 2020 13:25:40 -0800 Subject: [PATCH 16/18] polish --- docs/use.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/use.md b/docs/use.md index 9cdaad52..f870e0ec 100644 --- a/docs/use.md +++ b/docs/use.md @@ -126,6 +126,7 @@ function ProfileCard() { const userDetailsRef = useFirestore() .collection('users') .doc(user.uid); + let { commonName, favoriteAnimal, profileImagePath } = useFirestoreDocData( userDetailsRef ); @@ -133,6 +134,10 @@ function ProfileCard() { // defend against null field(s) profileImagePath = profileImagePath || DEFAULT_IMAGE_PATH; + if (!commonName || !favoriteAnimal) { + throw new Error(MissingProfileInfoError); + } + return (

{commonName}

@@ -261,7 +266,7 @@ function FoodRatings() { } ``` -### Solve `Warning: App triggered a user-blocking update that suspended. with useTransition` +### Solve `Warning: App triggered a user-blocking update that suspended.` with useTransition This warning can be solved with React's `useTransition` hook. Check out the sample code's Firestore example to see how to use this with ReactFire: From 20452f921db19cdbb6318668e778ce94b5e74380 Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Tue, 25 Feb 2020 11:42:52 -0800 Subject: [PATCH 17/18] fix useobservable link --- docs/reference.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/reference.md b/docs/reference.md index 556c784a..4d138b58 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -35,7 +35,7 @@ - Cloud Storage - [`useStorageTask`](#useStorageTask) - [`useStorageDownloadURL`](#useStorageDownloadURL) - - [`useObservable`](#useObservable) + - [`useObservable`](#useobservablet) - [ReactFireOptions](#ReactFireOptions) - [Components](#Components) @@ -371,6 +371,8 @@ _Throws a Promise by default_ Subscribe to a storage blob's download URL +useobservable link + _Throws a Promise by default_ ### Parameters From 78a0b60918a735d0417c1a2f5fd94818535c8cfc Mon Sep 17 00:00:00 2001 From: jhuleatt Date: Tue, 25 Feb 2020 11:48:50 -0800 Subject: [PATCH 18/18] Address James and David's in-person feedback --- README.md | 10 +++++----- docs/quickstart.md | 6 +++--- docs/use.md | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9f03ecfd..c3e61b17 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# Reactfire +# ReactFire Hooks, Context Providers, and Components that make it easy to interact with Firebase. -⚠️ **Status: Alpha**. ReactFire is meant for React Concurrent Mode, which is only +⚠️ **Status: Experimental**. The API is intended to be stable, but ReactFire is meant for React Concurrent Mode, which is only available in [experimental React builds](https://reactjs.org/docs/concurrent-mode-adoption.html#installation). @@ -12,7 +12,7 @@ available in - **Easy realtime updates for your function components** - Hooks like `useUser`and `useFirestoreCollection` let you easily subscribe to auth state, realtime data, and all other Firebase SDK events. Plus, they automatically unsubscribe when your component unmounts. -- **Loading states handled by ``** - Reactfire's hooks throw promises +- **Loading states handled by ``** - ReactFire's hooks throw promises that Suspense can catch. No more `isLoaded ?...` - let React [handle it for you](https://reactjs.org/docs/concurrent-mode-suspense.html). - **Faster initial page load times** - Load only the code you need, when you need it, with `useFirestore`, `useAuth`, `useRemoteConfig`, and more. @@ -22,10 +22,10 @@ available in ```bash # npm -npm install --save reactfire +npm install --save reactfire firebase # yarn -yarn add reactfire +yarn add reactfire firebase ``` - [**Quickstart**](./docs/quickstart.md) diff --git a/docs/quickstart.md b/docs/quickstart.md index 888946d8..96fefe0e 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,4 +1,4 @@ -# Reactfire Quickstart +# ReactFire Quickstart ⚛ + 🔥 = 🌯 @@ -15,7 +15,7 @@ npx create-react-app myapp cd myapp ``` -## 2. Install reactfire and the Firebase SDK +## 2. Install ReactFire and the Firebase SDK ```bash # yarn @@ -48,7 +48,7 @@ npm install --save firebase reactfire ## 4. Modify `src/index.js` -1. Import firebase and reactfire +1. Import Firebase and ReactFire ```js //... diff --git a/docs/use.md b/docs/use.md index f870e0ec..bdf5d26b 100644 --- a/docs/use.md +++ b/docs/use.md @@ -17,7 +17,7 @@ ## Access your `firebase` app from any component -Since reactfire uses React's Context API, any component under a `FirebaseAppProvider` can use `useFirebaseApp()` to get your initialized app. Plus, all ReactFire hooks will automatically check context to see if a firebase app is available. +Since ReactFire uses React's Context API, any component under a `FirebaseAppProvider` can use `useFirebaseApp()` to get your initialized app. Plus, all ReactFire hooks will automatically check context to see if a firebase app is available. ```jsx // ** INDEX.JS ** @@ -45,7 +45,7 @@ function MyComponent(props) { ## Access the current user -The `useUser()` hook returns the currently signed-in [user](https://firebase.google.com/docs/reference/js/firebase.User). Like the other Reactfire Hooks, you need to wrap it in `Suspense` or provide a `startWithValue`. +The `useUser()` hook returns the currently signed-in [user](https://firebase.google.com/docs/reference/js/firebase.User). Like the other ReactFire Hooks, you need to wrap it in `Suspense` or provide a `startWithValue`. ```jsx function HomePage(props) { @@ -178,7 +178,7 @@ function ProfilePage() { ## Manage Loading States -Reactfire is designed to integrate with React's Suspense API, but also supports use cases where Suspense isn't needed or wanted. +ReactFire is designed to integrate with React's Suspense API, but also supports use cases where Suspense isn't needed or wanted. ### Default: `Suspense` @@ -216,7 +216,7 @@ function FoodRatings() { #### Bonus: `SuspenseWithPerf` -Reactfire provides an a wrapper around `Suspense` called `SuspenseWithPerf` that instruments your `Suspense` loads with a Firebase Performance Monitoring custom trace. It looks like this: +ReactFire provides an a wrapper around `Suspense` called `SuspenseWithPerf` that instruments your `Suspense` loads with a Firebase Performance Monitoring custom trace. It looks like this: ```jsx function FoodRatings() { @@ -233,7 +233,7 @@ function FoodRatings() { ### Don't want Suspense? Provide an initial value -What if we don't want to use Suspense, or we're server rendering and we know what the initial value should be? In that case we can provide an initial value to any Reactfire hook: +What if we don't want to use Suspense, or we're server rendering and we know what the initial value should be? In that case we can provide an initial value to any ReactFire hook: ```jsx function Burrito() {