diff --git a/README.md b/README.md
index 596cce68..c3e61b17 100644
--- a/README.md
+++ b/README.md
@@ -1,61 +1,59 @@
-# Reactfire
+# ReactFire
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: 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).
## 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.
-- **Loading states handled by ``** - Reactfire's hooks throw promises
+- **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
```bash
# npm
-npm install --save reactfire
+npm install --save reactfire firebase
# yarn
-yarn add reactfire
+yarn add reactfire firebase
```
+- [**Quickstart**](./docs/quickstart.md)
+- [**Common Use Cases**](./docs/use.md)
+- [**API Reference**](./docs/reference.md)
+
## 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';
-import { render } from 'react-dom';
-import './style.css';
+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');
@@ -65,18 +63,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'}
>
@@ -85,30 +80,13 @@ 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
-
-- [**Quickstart**](./docs/quickstart.md)
-- [**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
+> uses mixins), click
+> [here](https://github.com/FirebaseExtended/reactfire/tree/v1.0.0)
diff --git a/docs/quickstart.md b/docs/quickstart.md
index ad8ac64d..96fefe0e 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -1,12 +1,12 @@
-# Reactfire Quickstart
+# ReactFire Quickstart
⚛ + 🔥 = 🌯
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.
+## 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.
@@ -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,12 +48,11 @@ npm install --save firebase reactfire
## 4. Modify `src/index.js`
-1. Import firebase and reactfire
+1. Import Firebase and 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
+ */}
`
+
+_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 +598,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>`
diff --git a/docs/use.md b/docs/use.md
index b66af46c..bdf5d26b 100644
--- a/docs/use.md
+++ b/docs/use.md
@@ -1,20 +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-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](#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)
-- [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)
+ - [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](#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.
+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 **
@@ -31,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');
@@ -40,9 +43,142 @@ 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;
+
+ if (!commonName || !favoriteAnimal) {
+ throw new Error(MissingProfileInfoError);
+ }
+
+ 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.
+ReactFire is designed to integrate with React's Suspense API, but also supports use cases where Suspense isn't needed or wanted.
### Default: `Suspense`
@@ -80,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() {
@@ -95,9 +231,9 @@ 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:
+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() {
@@ -130,32 +266,11 @@ function FoodRatings() {
}
```
-## 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
+### Solve `Warning: App triggered a user-blocking update that suspended.` with useTransition
-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:
+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:
-```jsx
-render(
- }>
-
-
-);
-```
+https://github.com/FirebaseExtended/reactfire/blob/c67dfa755431c15034f0c713b9df3864fb762c06/sample/src/Firestore.js#L87-L121
## Lazy Load the Firebase SDKs
@@ -179,14 +294,28 @@ 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
+
+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(firebaseApp, 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`.
+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.
+
+## 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)!).