diff --git a/.firebaserc b/.firebaserc
new file mode 100644
index 0000000..7fe5f59
--- /dev/null
+++ b/.firebaserc
@@ -0,0 +1,5 @@
+{
+ "projects": {
+ "default": "demo-noop"
+ }
+}
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..d84813e
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,26 @@
+name: build
+on: push
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16
+ - name: Cache node modules
+ uses: actions/cache@v3
+ env:
+ cache-name: cache-node-modules
+ with:
+ # npm cache files are stored in `~/.npm` on Linux/macOS
+ path: ~/.npm
+ key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-build-${{ env.cache-name }}-
+ ${{ runner.os }}-build-
+ ${{ runner.os }}-
+ - name: Install
+ run: npm install
+ - name: Build
+ run: npm run build
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
new file mode 100644
index 0000000..bcfb022
--- /dev/null
+++ b/.github/workflows/checks.yml
@@ -0,0 +1,30 @@
+name: build
+on:
+ pull_request:
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16
+ cache: npm
+ - name: Install
+ run: npm install
+ - name: Build
+ run: npm run build
+ - name: Test
+ run: npm run test:ci
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16
+ cache: npm
+ - name: Install
+ run: npm install
+ - name: Test
+ run: npm run test:ci
diff --git a/.gitignore b/.gitignore
index 8bc1a7c..6f15b4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ coverage
#test files
/example
+fire*.log
+/.idea/
diff --git a/README.md b/README.md
index 46a0f6c..2d21e0c 100644
--- a/README.md
+++ b/README.md
@@ -5,34 +5,42 @@ A set of reusable [React Hooks](https://reactjs.org/docs/hooks-intro.html) for [
[](https://www.npmjs.com/package/react-firebase-hooks)
[](https://www.npmjs.com/package/react-firebase-hooks)
-**This documentation is for v3 of React Firebase Hooks which involved a number of breaking changes, including adding support for Firebase v8.0.0 - more details [here](https://github.com/CSFrequency/react-firebase-hooks/releases/tag/v3.0.0). For v2 documentation, see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v2.2.0).**
+This documentation is for v5 of React Firebase Hooks which requires Firebase v9 or higher.
+
+- For v4 documentation (Firebase v9), see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2).
+- For v3 documentation (Firebase v8), see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v3.0.5).
+- For v2 documentation, see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v2.2.0).
## Installation
-React Firebase Hooks v3 requires **React 16.8.0 or later** and **Firebase v8.0.0 or later**.
+React Firebase Hooks v4 requires **React 16.8.0 or later** and **Firebase v9.0.0 or later**.
-> Official support for Hooks was added to React Native in v0.59.0. React Firebase Hooks works with both the Firebase JS SDK and React Native Firebase, although some of the typings may be incorrect.
+> Whilst previous versions of React Firebase Hooks had some support for React Native Firebase, the underlying changes to v9 of the Firebase Web library have meant this is no longer as straightforward. We will investigate if this is possible in another way as part of a future release.
-```
+```bash
+# with npm
npm install --save react-firebase-hooks
+
+# with yarn
+yarn add react-firebase-hooks
```
-This assumes that you’re using the [npm](https://npmjs.com) package manager with a module bundler like [Webpack](https://webpack.js.org/) or [Browserify](http://browserify.org/) to consume [CommonJS](http://webpack.github.io/docs/commonjs.html) modules.
+This assumes that you’re using the [npm](https://npmjs.com) or [yarn](https://yarnpkg.com/) package managers with a module bundler like [Webpack](https://webpack.js.org/) or [Browserify](http://browserify.org/) to consume [CommonJS](http://webpack.github.io/docs/commonjs.html) modules.
## Why?
-There has been a **lot** of hype around React Hooks, but this hype merely reflects that there are obvious real world benefits of Hooks to React developers everywhere.
-
This library explores how React Hooks can work to make integration with Firebase even more straightforward than it already is. It takes inspiration for naming from RxFire and is based on an internal library that we had been using in a number of apps prior to the release of React Hooks. The implementation with hooks is 10x simpler than our previous implementation.
-## Upgrading from v2 to v3
+## Upgrading from v4 to v5
-To upgrade your project from v2 to v3 check out the [Release Notes](https://github.com/CSFrequency/react-firebase-hooks/releases/tag/v3.0.0) which have full details of everything that needs to be changed.
+To upgrade your project from v4 to v5 check out the [Release Notes](https://github.com/CSFrequency/react-firebase-hooks/releases/tag/v5.0.0) which have full details of everything that needs to be changed.
## Documentation
-- [Auth Hooks](/auth)
+- [Authentication Hooks](/auth)
- [Cloud Firestore Hooks](/firestore)
+- [Cloud Functions Hooks](/functions)
+- [Cloud Messaging Hooks](/messaging)
- [Cloud Storage Hooks](/storage)
- [Realtime Database Hooks](/database)
diff --git a/auth/README.md b/auth/README.md
index 821e999..4a9b2f2 100644
--- a/auth/README.md
+++ b/auth/README.md
@@ -1,6 +1,6 @@
# React Firebase Hooks - Auth
-React Firebase Hooks provides a convenience listener for Firebase Auth's auth state. The hook wraps around the `firebase.auth().onAuthStateChange()` method to ensure that it is always up to date.
+React Firebase Hooks provides a convenience listener for Firebase Auth's auth state. The hook wraps around the `auth.onAuthStateChange(...)` method to ensure that it is always up to date.
All hooks can be imported from `react-firebase-hooks/auth`, e.g.
@@ -11,43 +11,131 @@ import { useAuthState } from 'react-firebase-hooks/auth';
List of Auth hooks:
- [useAuthState](#useauthstate)
+- [useIdToken](#useidtoken)
- [useCreateUserWithEmailAndPassword](#usecreateuserwithemailandpassword)
- [useSignInWithEmailAndPassword](#usesigninwithemailandpassword)
+- [useSignInWithApple](#usesigninwithapple)
+- [useSignInWithFacebook](#usesigninwithfacebook)
+- [useSignInWithGithub](#usesigninwithgithub)
+- [useSignInWithGoogle](#usesigninwithgoogle)
+- [useSignInWithMicrosoft](#usesigninwithmicrosoft)
+- [useSignInWithTwitter](#usesigninwithtwitter)
+- [useSignInWithYahoo](#usesigninwithyahoo)
+- [useSendSignInLinkToEmail](#usesendsigninlinktoemail)
+- [useUpdateEmail](#useupdateemail)
+- [useUpdatePassword](#useupdatepassword)
+- [useUpdateProfile](#useupdateprofile)
+- [useVerifyBeforeUpdateEmail](#useverifybeforeupdateemail)
+- [useSendPasswordResetEmail](#usesendpasswordresetemail)
+- [useSendEmailVerification](#usesendemailverification)
+- [useSignOut](#usesignout)
+- [useDeleteUser](#usedeleteuser)
### useAuthState
```js
-const [user, loading, error] = useAuthState(auth);
+const [user, loading, error] = useAuthState(auth, options);
```
-Retrieve and monitor the authentication state from Firebase.
+Retrieve and monitor the authentication state from Firebase. Uses `auth.onAuthStateChanged` so is only triggered when a user signs in or signs out. See [useIdToken](#useidtoken) if you need to monitor token changes too.
The `useAuthState` hook takes the following parameters:
-- `auth`: `firebase.auth.Auth` instance for the app you would like to monitor
+- `auth`: `auth.Auth` instance for the app you would like to monitor
+- `options`: (optional) `Object with the following parameters:
+ - `onUserChanged`: (optional) function to be called with `auth.User` each time the user changes. This allows you to do things like load custom claims.
Returns:
-- `user`: The `firebase.User` if logged in, or `undefined` if not
-- `loading`: A `boolean` to indicate whether the the authentication state is still being loaded
-- `error`: Any `firebase.auth.Error` returned by Firebase when trying to load the user, or `undefined` if there is no error
+- `user`: The `auth.UserCredential` if logged in, or `null` if not
+- `loading`: A `boolean` to indicate whether the authentication state is still being loaded
+- `error`: Any `AuthError` returned by Firebase when trying to load the user, or `undefined` if there is no error
#### If you are registering or signing in the user for the first time consider using [useCreateUserWithEmailAndPassword](#usecreateuserwithemailandpassword), [useSignInWithEmailAndPassword](#usesigninwithemailandpassword)
#### Full Example
```js
+import { getAuth, signInWithEmailAndPassword, signOut } from 'firebase/auth';
import { useAuthState } from 'react-firebase-hooks/auth';
+const auth = getAuth(firebaseApp);
+
+const login = () => {
+ signInWithEmailAndPassword(auth, 'test@test.com', 'password');
+};
+const logout = () => {
+ signOut(auth);
+};
+
+const CurrentUser = () => {
+ const [user, loading, error] = useAuthState(auth);
+
+ if (loading) {
+ return (
+
+
Initialising User...
+
+ );
+ }
+ if (error) {
+ return (
+
+
Error: {error}
+
+ );
+ }
+ if (user) {
+ return (
+
+
Current User: {user.user.email}
+
+
+ );
+ }
+ return ;
+};
+```
+
+### useIdToken
+
+```js
+const [user, loading, error] = useIdToken(auth, options);
+```
+
+Retrieve and monitor changes to the ID token from Firebase. Uses `auth.onIdTokenChanged` so includes when a user signs in, signs out or token refresh events.
+
+The `useIdToken` hook takes the following parameters:
+
+- `auth`: `auth.Auth` instance for the app you would like to monitor
+- `options`: (optional) `Object with the following parameters:
+ - `onUserChanged`: (optional) function to be called with `auth.User` each time the user changes. This allows you to do things like load custom claims.
+
+Returns:
+
+- `user`: The `auth.UserCredential` if logged in, or `null` if not
+- `loading`: A `boolean` to indicate whether the authentication state is still being loaded
+- `error`: Any `AuthError` returned by Firebase when trying to load the user, or `undefined` if there is no error
+
+#### If you are registering or signing in the user for the first time consider using [useCreateUserWithEmailAndPassword](#usecreateuserwithemailandpassword), [useSignInWithEmailAndPassword](#usesigninwithemailandpassword)
+
+#### Full Example
+
+```js
+import { getAuth, signInWithEmailAndPassword, signOut } from 'firebase/auth';
+import { useIdToken } from 'react-firebase-hooks/auth';
+
+const auth = getAuth(firebaseApp);
+
const login = () => {
- firebase.auth().signInWithEmailAndPassword('test@test.com', 'password');
+ signInWithEmailAndPassword(auth, 'test@test.com', 'password');
};
const logout = () => {
- firebase.auth().signOut();
+ signOut(auth);
};
const CurrentUser = () => {
- const [user, loading, error] = useAuthState(firebase.auth());
+ const [user, loading, error] = useIdToken(auth);
if (loading) {
return (
@@ -66,7 +154,7 @@ const CurrentUser = () => {
if (user) {
return (
-
Current User: {user.email}
+
Current User: {user.user.email}
);
@@ -90,19 +178,22 @@ Create a user with email and password. Wraps the underlying `firebase.auth().cre
The `useCreateUserWithEmailAndPassword` hook takes the following parameters:
-- `auth`: `firebase.auth.Auth` instance for the app you would like to monitor
+- `auth`: `auth.Auth` instance for the app you would like to monitor
+- `options`: (optional) `Object` with the following parameters:
+ - `emailVerificationOptions`: (optional) `auth.ActionCodeSettings` to customise the email verification
+ - `sendEmailVerification`: (optional) `boolean` to trigger sending of an email verification after the user has been created
Returns:
-- `createUserWithEmailAndPassword(email: string, password: string)`: a function you can call to start the registration
-- `user`: The `firebase.User` if the user was created or `undefined` if not
+- `createUserWithEmailAndPassword(email: string, password: string) => Promise`: A function you can call to start the registration. Returns the `auth.UserCredential` if the user was created successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was created or `undefined` if not
- `loading`: A `boolean` to indicate whether the the user creation is processing
-- `error`: Any `firebase.auth.Error` returned by Firebase when trying to create the user, or `undefined` if there is no error
+- `error`: Any `Error` returned by Firebase when trying to create the user, or `undefined` if there is no error
#### Full Example
```jsx
-import { useSignInWithEmailAndPassword } from 'react-firebase-hooks/auth';
+import { useCreateUserWithEmailAndPassword } from 'react-firebase-hooks/auth';
const SignIn = () => {
const [email, setEmail] = useState('');
@@ -127,7 +218,7 @@ const SignIn = () => {
if (user) {
return (
-
Registered User: {user.email}
+
Registered User: {user.user.email}
);
}
@@ -159,21 +250,21 @@ const [
user,
loading,
error,
-] = useSignInWithEmailAndPassword(auth, email, password);
+] = useSignInWithEmailAndPassword(auth);
```
-Login a user with email and password. Wraps the underlying `firebase.auth().signInWithEmailAndPassword` method and provides additional `loading` and `error` information.
+Login a user with email and password. Wraps the underlying `auth.signInWithEmailAndPassword` method and provides additional `loading` and `error` information.
The `useSignInWithEmailAndPassword` hook takes the following parameters:
-- `auth`: `firebase.auth.Auth` instance for the app you would like to monitor
+- `auth`: `Auth` instance for the app you would like to monitor
Returns:
-- `signInWithEmailAndPassword(email: string, password: string)`: a function you can call to start the login
-- `user`: The `firebase.User` if the user was logged in or `undefined` if not
+- `signInWithEmailAndPassword(email: string, password: string) => Promise`: A function you can call to start the login. Returns the `auth.UserCredential` if the user was signed in successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was logged in or `undefined` if not
- `loading`: A `boolean` to indicate whether the the user login is processing
-- `error`: Any `firebase.auth.Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
+- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
#### Full Example
@@ -226,3 +317,845 @@ const SignIn = () => {
);
};
```
+
+### useSignInWithApple
+
+```js
+const [signInWithApple, user, loading, error] = useSignInWithApple(auth);
+```
+
+Login a user with Apple Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information.
+
+The `useSignInWithApple` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `signInWithApple(scopes: string[], customOAuthParameters: auth.CustomParameters) => Promise`: A function you can call to start the login. Returns the `auth.UserCredential` if the user was signed in successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was logged in or `undefined` if not
+- `loading`: A `boolean` to indicate whether the the user login is processing
+- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
+
+#### Full example
+
+See [social login example](#social-login-example)
+
+### useSignInWithFacebook
+
+```js
+const [signInWithFacebook, user, loading, error] = useSignInWithFacebook(auth);
+```
+
+Login a user with Facebook Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information.
+
+The `useSignInWithFacebook` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `signInWithFacebook(scopes: string[], customOAuthParameters: auth.CustomParameters) => Promise`: A function you can call to start the login. Returns the `auth.UserCredential` if the user was signed in successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was logged in or `undefined` if not
+- `loading`: A `boolean` to indicate whether the the user login is processing
+- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
+
+#### Full example
+
+See [social login example](#social-login-example)
+
+### useSignInWithGithub
+
+```js
+const [signInWithGithub, user, loading, error] = useSignInWithGithub(auth);
+```
+
+Login a user with Github Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information.
+
+The `useSignInWithGithub` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `signInWithGithub(scopes: string[], customOAuthParameters: auth.CustomParameters) => Promise`: A function you can call to start the login. Returns the `auth.UserCredential` if the user was signed in successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was logged in or `undefined` if not
+- `loading`: A `boolean` to indicate whether the the user login is processing
+- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
+
+#### Full example
+
+See [social login example](#social-login-example)
+
+### useSignInWithGoogle
+
+```js
+const [signInWithGoogle, user, loading, error] = useSignInWithGoogle(auth);
+```
+
+Login a user with Google Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.GoogleProvider` and provides additional `loading` and `error` information.
+
+The `useSignInWithGoogle` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `signInWithGoogle(scopes: string[], customOAuthParameters: auth.CustomParameters) => Promise`: A function you can call to start the login. Returns the `auth.UserCredential` if the user was signed in successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was logged in or `undefined` if not
+- `loading`: A `boolean` to indicate whether the the user login is processing
+- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
+
+#### Full example
+
+See [social login example](#social-login-example)
+
+### useSignInWithMicrosoft
+
+```js
+const [signInWithMicrosoft, user, loading, error] = useSignInWithMicrosoft(
+ auth
+);
+```
+
+Login a user with Microsoft Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information.
+
+The `useSignInWithMicrosoft` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `signInWithMicrosoft(scopes: string[], customOAuthParameters: auth.CustomParameters) => Promise`: A function you can call to start the login. Returns the `auth.UserCredential` if the user was signed in successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was logged in or `undefined` if not
+- `loading`: A `boolean` to indicate whether the the user login is processing
+- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
+
+#### Full example
+
+See [social login example](#social-login-example)
+
+### useSignInWithTwittter
+
+```js
+const [signInWithTwitter, user, loading, error] = useSignInWithTwitter(auth);
+```
+
+Login a user with Twitter Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information.
+
+The `useSignInWithTwitter` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `signInWithTwitter(scopes: string[], customOAuthParameters: auth.CustomParameters) => Promise`: A function you can call to start the login. Returns the `auth.UserCredential` if the user was signed in successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was logged in or `undefined` if not
+- `loading`: A `boolean` to indicate whether the the user login is processing
+- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
+
+#### Full example
+
+See [social login example](#social-login-example)
+
+### useSignInWithYahoo
+
+```js
+const [signInWithYahoo, user, loading, error] = useSignInWithYahoo(auth);
+```
+
+Login a user with Yahoo Authentication. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information.
+
+The `useSignInWithYahoo` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `signInWithYahoo(scopes: string[], customOAuthParameters: auth.CustomParameters) => Promise`: A function you can call to start the login. Returns the `auth.UserCredential` if the user was signed in successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was logged in or `undefined` if not
+- `loading`: A `boolean` to indicate whether the the user login is processing
+- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
+
+#### Full example
+
+See [social login example](#social-login-example)
+
+### Social Login Example
+
+```jsx
+import { useSignInWithXXX } from 'react-firebase-hooks/auth';
+
+const SignIn = () => {
+ const [signInWithXXX, user, loading, error] = useSignInWithXXX(auth);
+
+ if (error) {
+ return (
+
+ );
+};
+```
+
+### useSendSignInLinkToEmail
+
+```js
+const [sendSignInLinkToEmail, sending, error] = useSendSignInLinkToEmail(auth);
+```
+
+Sends a sign-in email link to the user with the specified email. Wraps the underlying `auth.sendSignInLinkToEmail` method and provides additional `sending` and `error` information.
+
+To complete sign in with the email link use the [useSignInWithEmailLink](#usesigninwithemaillink) hook.
+
+The `useSendSignInLinkToEmail` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `sendSignInLinkToEmail(email: string, actionCodeSettings: ActionCodeSettings) => Promise`: A function you can call to send a sign-in email link to an email. Requires an [actionCodeSettings](https://firebase.google.com/docs/reference/js/auth.actioncodesettings.md#actioncodesettings_interface) object. Returns `true` if the function was successful, or `false` if there was an error.
+- `sending`: A `boolean` to indicate whether the email is being sent
+- `error`: Any `Error` returned by Firebase when trying to send the email, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useSendSignInLinkToEmail } from 'react-firebase-hooks/auth';
+
+const SendSignInLinkToEmail = () => {
+ const [email, setEmail] = useState('');
+ const [sendSignInLinkToEmail, sending, error] = useSendSignInLinkToEmail(
+ auth
+ );
+
+ const actionCodeSettings = {
+ // The URL to redirect to for sign-in completion. This is also the deep
+ // link for mobile redirects. The domain (www.example.com) for this URL
+ // must be whitelisted in the Firebase Console.
+ url: 'https://www.example.com/finishSignUp?cartId=1234',
+ iOS: {
+ bundleId: 'com.example.ios',
+ },
+ android: {
+ packageName: 'com.example.android',
+ installApp: true,
+ minimumVersion: '12',
+ },
+ // This must be true.
+ handleCodeInApp: true,
+ };
+
+ if (error) {
+ return (
+
+
Error: {error.message}
+
+ );
+ }
+ if (sending) {
+ return
Sending...
;
+ }
+ return (
+
+ setEmail(e.target.value)}
+ />
+
+
+ );
+};
+```
+
+### useSignInWithEmailLink
+
+```js
+const [signInWithEmailLink, user, loading, error] = useSignInWithEmailLink(
+ auth
+);
+```
+
+Login a user using an email and sign-in email link. Wraps the underlying `auth.signInWithEmailLink` method and provides additional `loading` and `error` information.
+
+The `useSignInWithEmailLink` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `signInWithEmailLink(email: string, emailLink?: string) => Promise`: A function you can call to start the login. If no `emailLink` is supplied, the link is inferred from the current URL. Returns the `auth.UserCredential` if the user was signed in successfully, or `undefined` if there was an error.
+- `user`: The `auth.UserCredential` if the user was logged in or `undefined` if not
+- `loading`: A `boolean` to indicate whether the the user login is processing
+- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useSignInWithEmailLink } from 'react-firebase-hooks/auth';
+
+const SignIn = () => {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [
+ signInWithEmailLink
+ user,
+ loading,
+ error,
+ ] = useSignInWithEmailLink(auth);
+
+ if (error) {
+ return (
+
+ );
+};
+```
+
+### useUpdateEmail
+
+```js
+const [updateEmail, updating, error] = useUpdateEmail(auth);
+```
+
+Update the current user's email address. Wraps the underlying `auth.updateEmail` method and provides additional `updating` and `error` information.
+
+The `useUpdateEmail` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `updateEmail(email: string) => Promise`: A function you can call to update the current user's email address. Returns `true` if the function was successful, or `false` if there was an error.
+- `updating`: A `boolean` to indicate whether the user update is processing
+- `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useUpdateEmail } from 'react-firebase-hooks/auth';
+
+const UpdateEmail = () => {
+ const [email, setEmail] = useState('');
+ const [updateEmail, updating, error] = useUpdateEmail(auth);
+
+ if (error) {
+ return (
+
+
Error: {error.message}
+
+ );
+ }
+ if (updating) {
+ return
Updating...
;
+ }
+ return (
+
+ setEmail(e.target.value)}
+ />
+
+
+ );
+};
+```
+
+### useUpdatePassword
+
+```js
+const [updatePassword, updating, error] = useUpdatePassword(auth);
+```
+
+Update the current user's password. Wraps the underlying `auth.updatePassword` method and provides additional `updating` and `error` information.
+
+The `useUpdatePassword` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `updatePassword(password: string) => Promise`: A function you can call to update the current user's password. Returns `true` if the function was successful, or `false` if there was an error.
+- `updating`: A `boolean` to indicate whether the user update is processing
+- `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useUpdatePassword } from 'react-firebase-hooks/auth';
+
+const UpdatePassword = () => {
+ const [password, setPassword] = useState('');
+ const [updatePassword, updating, error] = useUpdatePassword(auth);
+
+ if (error) {
+ return (
+
+
Error: {error.message}
+
+ );
+ }
+ if (updating) {
+ return
Updating...
;
+ }
+ return (
+
+ setPassword(e.target.value)}
+ />
+
+
+ );
+};
+```
+
+### useUpdateProfile
+
+```js
+const [updateProfile, updating, error] = useUpdateProfile(auth);
+```
+
+Update the current user's profile. Wraps the underlying `auth.updateProfile` method and provides additional `updating` and `error` information.
+
+The `useUpdateProfile` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `updateProfile({ displayName: string, photoURL: string }) => Promise`: A function you can call to update the current user's profile. Returns `true` if the function was successful, or `false` if there was an error.
+- `updating`: A `boolean` to indicate whether the user update is processing
+- `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useUpdateProfile } from 'react-firebase-hooks/auth';
+
+const UpdateProfile = () => {
+ const [displayName, setDisplayName] = useState('');
+ const [photoURL, setPhotoURL] = useState('');
+ const [updateProfile, updating, error] = useUpdateProfile(auth);
+
+ if (error) {
+ return (
+
+ );
+};
+```
+
+### useVerifyBeforeUpdateEmail
+
+```js
+const [verifyBeforeUpdateEmail, updating, error] = useVerifyBeforeUpdateEmail(
+ auth
+);
+```
+
+Verify and update the current user's email address. Wraps the underlying `auth.verifyBeforeUpdateEmail` method and provides additional `updating` and `error` information.
+
+The `useVerifyBeforeUpdateEmail` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `verifyBeforeUpdateEmail(email: string, actionCodeSettings: ActionCodeSettings | null) => Promise`: A function you can call to verify and update the current user's email address. Returns `true` if the function was successful, or `false` if there was an error.
+- `updating`: A `boolean` to indicate whether the user update is processing
+- `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useVerifyBeforeUpdateEmail } from 'react-firebase-hooks/auth';
+
+const VerifyBeforeUpdateEmail = () => {
+ const [email, setEmail] = useState('');
+ const [verifyBeforeUpdateEmail, updating, error] = useVerifyBeforeUpdateEmail(
+ auth
+ );
+
+ if (error) {
+ return (
+
+
Error: {error.message}
+
+ );
+ }
+ if (updating) {
+ return
Updating...
;
+ }
+ return (
+
+ setEmail(e.target.value)}
+ />
+
+
+ );
+};
+```
+
+### useSendPasswordResetEmail
+
+```js
+const [sendPasswordResetEmail, sending, error] = useSendPasswordResetEmail(
+ auth
+);
+```
+
+Send a password reset email to the specified email address. Wraps the underlying `auth.sendPasswordResetEmail` method and provides additional `sending` and `error` information.
+
+The `useSendPasswordResetEmail` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `sendPasswordResetEmail(email: string, actionCodeSettings?: ActionCodeSettings) => Promise`: A function you can call to send a password reset email. Optionally accepts an [actionCodeSettings](https://firebase.google.com/docs/reference/js/auth.actioncodesettings.md#actioncodesettings_interface) object. Returns `true` if the function was successful, or `false` if there was an error.
+- `sending`: A `boolean` to indicate whether the email is being sent
+- `error`: Any `Error` returned by Firebase when trying to send the email, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useSendPasswordResetEmail } from 'react-firebase-hooks/auth';
+
+const SendPasswordReset = () => {
+ const [email, setEmail] = useState('');
+ const [sendPasswordResetEmail, sending, error] = useSendPasswordResetEmail(
+ auth
+ );
+
+ const actionCodeSettings = {
+ url: 'https://www.example.com/login',
+ };
+
+ if (error) {
+ return (
+
+
Error: {error.message}
+
+ );
+ }
+ if (sending) {
+ return
Sending...
;
+ }
+ return (
+
+ setEmail(e.target.value)}
+ />
+
+
+ );
+};
+```
+
+### useSendEmailVerification
+
+```js
+const [sendEmailVerification, sending, error] = useSendEmailVerification(auth);
+```
+
+Send a verification email to the current user. Wraps the underlying `auth.sendEmailVerification` method and provides additional `sending` and `error` information.
+
+The `useSendEmailVerification` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `sendEmailVerification() => Promise`: A function you can call to send a password reset email. Returns `true` if the function was successful, or `false` if there was an error.
+- `sending`: A `boolean` to indicate whether the email is being sent
+- `error`: Any `Error` returned by Firebase when trying to send the email, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useSendEmailVerification } from 'react-firebase-hooks/auth';
+
+const SendEmailVerification = () => {
+ const [email, setEmail] = useState('');
+ const [sendEmailVerification, sending, error] = useSendEmailVerification(
+ auth
+ );
+
+ if (error) {
+ return (
+
+
Error: {error.message}
+
+ );
+ }
+ if (sending) {
+ return
Sending...
;
+ }
+ return (
+
+
+
+ );
+};
+```
+
+### useSignOut
+
+```js
+const [signOut, loading, error] = useSignOut(auth);
+```
+
+Sign out current user. Wraps the underlying `auth.signOut` method and provides additional `loading` and `error` information.
+
+The `useSignOut` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `signOut() => Promise`: A function you can call to sign out current user. Returns `true` if the function was successful, or `false` if there was an error.
+- `loading`: A `boolean` to indicate whether the user is being signed out
+- `error`: Any `Error` returned by Firebase when trying to sign out user, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useSignOut } from 'react-firebase-hooks/auth';
+
+const SignOut = () => {
+ const [signOut, loading, error] = useSignOut(auth);
+
+ if (error) {
+ return (
+
+
Error: {error.message}
+
+ );
+ }
+ if (loading) {
+ return
Loading...
;
+ }
+ return (
+
+
+
+ );
+};
+```
+
+### useDeleteUser
+
+```js
+const [deleteUser, loading, error] = useDeleteUser(auth);
+```
+
+Delete current user. Wraps the underlying `auth.currrentUser.delete` method and provides additional `loading` and `error` information.
+
+The `useDeleteUser` hook takes the following parameters:
+
+- `auth`: `Auth` instance for the app you would like to monitor
+
+Returns:
+
+- `deleteUser() => Promise`: A function you can call to delete the current user. Returns `true` if the function was successful, or `false` if there was an error.
+- `loading`: A `boolean` to indicate whether the deletion is processing
+- `error`: Any `Error` returned by Firebase when trying to delete user, or `undefined` if there is no error
+
+#### Full Example
+
+```jsx
+import { useDeleteUser } from 'react-firebase-hooks/auth';
+
+const DeleteUser = () => {
+ const [deleteUser, loading, error] = useDeleteUser(auth);
+
+ if (error) {
+ return (
+
@@ -77,40 +85,40 @@ const DatabaseList = () => {
const [keys, loading, error] = useListKeys(reference);
```
-As `useList`, but this hooks extracts the `firebase.database.DataSnapshot.key` values, rather than the the `firebase.database.DataSnapshot`s themselves.
+As `useList`, but this hooks extracts the `database.DataSnapshot.key` values, rather than the the `database.DataSnapshot`s themselves.
The `useListKeys` hook takes the following parameters:
-- `reference`: (optional) `firebase.database.Reference` for the data you would like to load
+- `reference`: (optional) `database.Reference` for the data you would like to load
Returns:
- `keys`: an array of `string`, or `undefined` if no reference is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `firebase.FirebaseError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
### useListVals
```js
-const [values, loading, error] = useListVals < T > (reference, options);
+const [values, loading, error] = useListVals (reference, options);
```
-As `useList`, but this hook extracts a typed list of the `firebase.database.DataSnapshot.val()` values, rather than the the
-`firebase.database.DataSnapshot`s themselves.
+As `useList`, but this hook extracts a typed list of the `database.DataSnapshot.val()` values, rather than the the
+`database.DataSnapshot`s themselves.
The `useListVals` hook takes the following parameters:
-- `reference`: (optional) `firebase.database.Reference` for the data you would like to load
+- `reference`: (optional) `database.Reference` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `keyField`: (optional) `string` field name that should be populated with the `firebase.database.DataSnapshot.id` property in the returned values.
- - `refField`: (optional) `string` field name that should be populated with the `firebase.database.DataSnapshot.ref` property.
- - `transform`: (optional) a function that receives the raw `firebase.database.DataSnapshot.val()` for each item in the list to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below.
+ - `keyField`: (optional) `string` field name that should be populated with the `database.DataSnapshot.id` property in the returned values.
+ - `refField`: (optional) `string` field name that should be populated with the `database.DataSnapshot.ref` property.
+ - `transform`: (optional) a function that receives the raw `database.DataSnapshot.val()` for each item in the list to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below.
Returns:
- `values`: an array of `T`, or `undefined` if no reference is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `firebase.FirebaseError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
### useObject
@@ -122,28 +130,31 @@ Retrieve and monitor an object or primitive value in the Firebase Realtime Datab
The `useObject` hook takes the following parameters:
-- `reference`: (optional) `firebase.database.Reference` for the data you would like to load
+- `reference`: (optional) `database.Reference` for the data you would like to load
Returns:
-- `snapshot`: a `firebase.database.DataSnapshot`, or `undefined` if no reference is supplied
+- `snapshot`: a `database.DataSnapshot`, or `undefined` if no reference is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `firebase.FirebaseError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
#### Full Example
```js
+import { ref, getDatabase } from 'firebase/database';
import { useObject } from 'react-firebase-hooks/database';
+const database = getDatabase(firebaseApp);
+
const DatabaseValue = () => {
- const [value, loading, error] = useObject(firebase.database().ref('value'));
+ const [snapshot, loading, error] = useObject(ref(database, 'value'));
return (
);
@@ -153,25 +164,25 @@ const DatabaseValue = () => {
### useObjectVal
```js
-const [value, loading, error] = useObjectVal < T > (reference, options);
+const [value, loading, error] = useObjectVal (reference, options);
```
-As `useObject`, but this hook returns the typed contents of `firebase.database.DataSnapshot.val()`, rather than the the
-`firebase.database.DataSnapshot` itself.
+As `useObject`, but this hook returns the typed contents of `database.DataSnapshot.val()`, rather than the the
+`database.DataSnapshot` itself.
The `useObjectVal` hook takes the following parameters:
-- `reference`: (optional) `firebase.database.Reference` for the data you would like to load
+- `reference`: (optional) `database.Reference` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `keyField`: (optional) `string` field name that should be populated with the `firebase.database.DataSnapshot.key` property in the returned value.
- - `refField`: (optional) `string` field name that should be populated with the `firebase.database.DataSnapshot.ref` property.
- - `transform`: (optional) a function that receives the raw `firebase.database.DataSnapshot.val()` to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below.
+ - `keyField`: (optional) `string` field name that should be populated with the `database.DataSnapshot.key` property in the returned value.
+ - `refField`: (optional) `string` field name that should be populated with the `database.DataSnapshot.ref` property.
+ - `transform`: (optional) a function that receives the raw `database.DataSnapshot.val()` to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below.
Returns:
- `value`: a `T`, or `undefined` if no reference is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `firebase.FirebaseError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `FirebaseError` returned by Firebase when trying to load the data, or `undefined` if there is no error
## Transforming data
diff --git a/database/helpers/index.ts b/database/helpers/index.ts
index 09c3fc3..81bde8e 100644
--- a/database/helpers/index.ts
+++ b/database/helpers/index.ts
@@ -1,4 +1,4 @@
-import firebase from 'firebase/app';
+import { DataSnapshot } from 'firebase/database';
export type ValOptions = {
keyField?: string;
@@ -10,7 +10,7 @@ const isObject = (val: any) =>
val != null && typeof val === 'object' && Array.isArray(val) === false;
export const snapshotToData = (
- snapshot: firebase.database.DataSnapshot,
+ snapshot: DataSnapshot,
keyField?: string,
refField?: string,
transform?: (val: any) => T
diff --git a/database/helpers/useListReducer.ts b/database/helpers/useListReducer.ts
index 6d85bd6..c60ef70 100644
--- a/database/helpers/useListReducer.ts
+++ b/database/helpers/useListReducer.ts
@@ -1,13 +1,13 @@
-import firebase from 'firebase/app';
+import { DataSnapshot } from 'firebase/database';
import { useReducer } from 'react';
type KeyValueState = {
keys?: string[];
- values?: firebase.database.DataSnapshot[];
+ values?: DataSnapshot[];
};
type ReducerState = {
- error?: firebase.FirebaseError;
+ error?: Error;
loading: boolean;
value: KeyValueState;
};
@@ -15,25 +15,25 @@ type ReducerState = {
type AddAction = {
type: 'add';
previousKey?: string | null;
- snapshot: firebase.database.DataSnapshot | null;
+ snapshot: DataSnapshot | null;
};
type ChangeAction = {
type: 'change';
- snapshot: firebase.database.DataSnapshot | null;
+ snapshot: DataSnapshot | null;
};
type EmptyAction = { type: 'empty' };
-type ErrorAction = { type: 'error'; error: firebase.FirebaseError };
+type ErrorAction = { type: 'error'; error: Error };
type MoveAction = {
type: 'move';
previousKey?: string | null;
- snapshot: firebase.database.DataSnapshot | null;
+ snapshot: DataSnapshot | null;
};
type RemoveAction = {
type: 'remove';
- snapshot: firebase.database.DataSnapshot | null;
+ snapshot: DataSnapshot | null;
};
type ResetAction = { type: 'reset' };
-type ValueAction = { type: 'value'; snapshots: firebase.database.DataSnapshot[] | null };
+type ValueAction = { type: 'value'; snapshots: DataSnapshot[] | null };
type ReducerAction =
| AddAction
| ChangeAction
@@ -126,7 +126,7 @@ const listReducer = (
}
};
-const setValue = (snapshots: firebase.database.DataSnapshot[] | null): KeyValueState => {
+const setValue = (snapshots: DataSnapshot[] | null): KeyValueState => {
if (!snapshots) {
return {
keys: [],
@@ -135,7 +135,7 @@ const setValue = (snapshots: firebase.database.DataSnapshot[] | null): KeyValueS
}
const keys: string[] = [];
- const values: firebase.database.DataSnapshot[] = [];
+ const values: DataSnapshot[] = [];
snapshots.forEach((snapshot) => {
if (!snapshot.key) {
return;
@@ -152,7 +152,7 @@ const setValue = (snapshots: firebase.database.DataSnapshot[] | null): KeyValueS
const addChild = (
currentState: KeyValueState,
- snapshot: firebase.database.DataSnapshot,
+ snapshot: DataSnapshot,
previousKey?: string | null
): KeyValueState => {
if (!snapshot.key) {
@@ -182,7 +182,7 @@ const addChild = (
const changeChild = (
currentState: KeyValueState,
- snapshot: firebase.database.DataSnapshot
+ snapshot: DataSnapshot
): KeyValueState => {
if (!snapshot.key) {
return currentState;
@@ -199,7 +199,7 @@ const changeChild = (
const removeChild = (
currentState: KeyValueState,
- snapshot: firebase.database.DataSnapshot
+ snapshot: DataSnapshot
): KeyValueState => {
if (!snapshot.key) {
return currentState;
@@ -217,7 +217,7 @@ const removeChild = (
const moveChild = (
currentState: KeyValueState,
- snapshot: firebase.database.DataSnapshot,
+ snapshot: DataSnapshot,
previousKey?: string | null
): KeyValueState => {
// Remove the child from it's previous location
diff --git a/database/index.js.flow b/database/index.js.flow
deleted file mode 100644
index 0a4201d..0000000
--- a/database/index.js.flow
+++ /dev/null
@@ -1,27 +0,0 @@
-// @flow
-import typeof { FirebaseError } from 'firebase';
-import type { DataSnapshot, Query } from 'firebase/database';
-
-type LoadingHook = [T | void, boolean, FirebaseError | void];
-
-export type ListHook = LoadingHook;
-export type ListKeysHook = LoadingHook;
-export type ListValsHook = LoadingHook;
-export type ObjectHook = LoadingHook;
-export type ObjectValHook = LoadingHook;
-
-declare export function useList(query?: Query | null): ListHook;
-declare export function useListKeys(query?: Query | null): ListKeysHook;
-declare export function useListVals(
- query?: Query | null,
- options?: {
- keyField?: string,
- }
-): ListValsHook;
-declare export function useObject(query?: Query | null): ObjectHook;
-declare export function useObjectVal(
- query?: Query | null,
- options?: {
- keyField?: string,
- }
-): ObjectValHook;
diff --git a/database/types.ts b/database/types.ts
index c99e88a..0ec2446 100644
--- a/database/types.ts
+++ b/database/types.ts
@@ -1,29 +1,23 @@
-import firebase from 'firebase/app';
+import { DatabaseReference, DataSnapshot } from 'firebase/database';
import { LoadingHook } from '../util';
export type Val<
T,
KeyField extends string = '',
RefField extends string = ''
-> = T & Record & Record;
+> = T & Record & Record;
-export type ObjectHook = LoadingHook<
- firebase.database.DataSnapshot,
- firebase.FirebaseError
->;
+export type ObjectHook = LoadingHook;
export type ObjectValHook<
T,
KeyField extends string = '',
RefField extends string = ''
-> = LoadingHook, firebase.FirebaseError>;
+> = LoadingHook, Error>;
-export type ListHook = LoadingHook<
- firebase.database.DataSnapshot[],
- firebase.FirebaseError
->;
-export type ListKeysHook = LoadingHook;
+export type ListHook = LoadingHook;
+export type ListKeysHook = LoadingHook;
export type ListValsHook<
T,
KeyField extends string = '',
RefField extends string = ''
-> = LoadingHook[], firebase.FirebaseError>;
+> = LoadingHook[], Error>;
diff --git a/database/useList.ts b/database/useList.ts
index ab4d08c..02e5170 100644
--- a/database/useList.ts
+++ b/database/useList.ts
@@ -1,15 +1,24 @@
-import firebase from 'firebase/app';
+import {
+ DataSnapshot,
+ off,
+ onChildAdded as firebaseOnChildAdded,
+ onChildChanged as firebaseOnChildChanged,
+ onChildMoved as firebaseOnChildMoved,
+ onChildRemoved as firebaseOnChildRemoved,
+ onValue as firebaseOnValue,
+ Query,
+} from 'firebase/database';
import { useEffect, useMemo } from 'react';
+import { useIsEqualRef } from '../util';
import { snapshotToData, ValOptions } from './helpers';
import useListReducer from './helpers/useListReducer';
import { ListHook, ListKeysHook, ListValsHook, Val } from './types';
-import { useIsEqualRef } from '../util';
-export const useList = (query?: firebase.database.Query | null): ListHook => {
+export const useList = (query?: Query | null): ListHook => {
const [state, dispatch] = useListReducer();
const queryRef = useIsEqualRef(query, () => dispatch({ type: 'reset' }));
- const ref: firebase.database.Query | null | undefined = queryRef.current;
+ const ref: Query | null | undefined = queryRef.current;
useEffect(() => {
if (!ref) {
@@ -18,41 +27,37 @@ export const useList = (query?: firebase.database.Query | null): ListHook => {
}
const onChildAdded = (
- snapshot: firebase.database.DataSnapshot | null,
+ snapshot: DataSnapshot | null,
previousKey?: string | null
) => {
dispatch({ type: 'add', previousKey, snapshot });
};
- const onChildChanged = (
- snapshot: firebase.database.DataSnapshot | null
- ) => {
+ const onChildChanged = (snapshot: DataSnapshot | null) => {
dispatch({ type: 'change', snapshot });
};
const onChildMoved = (
- snapshot: firebase.database.DataSnapshot | null,
+ snapshot: DataSnapshot | null,
previousKey?: string | null
) => {
dispatch({ type: 'move', previousKey, snapshot });
};
- const onChildRemoved = (
- snapshot: firebase.database.DataSnapshot | null
- ) => {
+ const onChildRemoved = (snapshot: DataSnapshot | null) => {
dispatch({ type: 'remove', snapshot });
};
- const onError = (error: firebase.FirebaseError) => {
+ const onError = (error: Error) => {
dispatch({ type: 'error', error });
};
- const onValue = (snapshots: firebase.database.DataSnapshot[] | null) => {
+ const onValue = (snapshots: DataSnapshot[] | null) => {
dispatch({ type: 'value', snapshots });
};
- let childAddedHandler: ReturnType | undefined;
- const onInitialLoad = (snapshot: firebase.database.DataSnapshot) => {
+ let childAddedHandler: ReturnType | undefined;
+ const onInitialLoad = (snapshot: DataSnapshot) => {
const snapshotVal = snapshot.val();
let childrenToProcess = snapshotVal
? Object.keys(snapshot.val()).length
@@ -60,14 +65,14 @@ export const useList = (query?: firebase.database.Query | null): ListHook => {
// If the list is empty then initialise the hook and use the default `onChildAdded` behaviour
if (childrenToProcess === 0) {
- childAddedHandler = ref.on('child_added', onChildAdded, onError);
+ childAddedHandler = firebaseOnChildAdded(ref, onChildAdded, onError);
onValue([]);
} else {
// Otherwise, we load the first batch of children all to reduce re-renders
- const children: firebase.database.DataSnapshot[] = [];
+ const children: DataSnapshot[] = [];
const onChildAddedWithoutInitialLoad = (
- addedChild: firebase.database.DataSnapshot,
+ addedChild: DataSnapshot,
previousKey?: string | null
) => {
if (childrenToProcess > 0) {
@@ -81,45 +86,42 @@ export const useList = (query?: firebase.database.Query | null): ListHook => {
return;
}
- onChildAdded(snapshot, previousKey);
+ onChildAdded(addedChild, previousKey);
};
- childAddedHandler = ref.on(
- 'child_added',
+ childAddedHandler = firebaseOnChildAdded(
+ ref,
onChildAddedWithoutInitialLoad,
onError
);
}
};
- ref.once('value', onInitialLoad, onError);
- const childChangedHandler = ref.on(
- 'child_changed',
+ firebaseOnValue(ref, onInitialLoad, onError, { onlyOnce: true });
+ const childChangedHandler = firebaseOnChildChanged(
+ ref,
onChildChanged,
onError
);
- const childMovedHandler = ref.on('child_moved', onChildMoved, onError);
- const childRemovedHandler = ref.on(
- 'child_removed',
+ const childMovedHandler = firebaseOnChildMoved(ref, onChildMoved, onError);
+ const childRemovedHandler = firebaseOnChildRemoved(
+ ref,
onChildRemoved,
onError
);
return () => {
- ref.off('child_added', childAddedHandler);
- ref.off('child_changed', childChangedHandler);
- ref.off('child_moved', childMovedHandler);
- ref.off('child_removed', childRemovedHandler);
+ off(ref, 'child_added', childAddedHandler);
+ off(ref, 'child_changed', childChangedHandler);
+ off(ref, 'child_moved', childMovedHandler);
+ off(ref, 'child_removed', childRemovedHandler);
};
}, [dispatch, ref]);
- const resArray: ListHook = [state.value.values, state.loading, state.error];
- return useMemo(() => resArray, resArray);
+ return [state.value.values, state.loading, state.error];
};
-export const useListKeys = (
- query?: firebase.database.Query | null
-): ListKeysHook => {
+export const useListKeys = (query?: Query | null): ListKeysHook => {
const [snapshots, loading, error] = useList(query);
const values = useMemo(
() =>
@@ -128,9 +130,8 @@ export const useListKeys = (
: undefined,
[snapshots]
);
- const resArray: ListKeysHook = [values, loading, error];
- return useMemo(() => resArray, resArray);
+ return [values, loading, error];
};
export const useListVals = <
@@ -138,12 +139,16 @@ export const useListVals = <
KeyField extends string = '',
RefField extends string = ''
>(
- query?: firebase.database.Query | null,
+ query?: Query | null,
options?: ValOptions
): ListValsHook => {
- const keyField = options ? options.keyField : undefined;
- const refField = options ? options.refField : undefined;
- const transform = options ? options.transform : undefined;
+ // Breaking this out in both `useList` and `useObject` rather than
+ // within the `snapshotToData` prevents the developer needing to ensure
+ // that `options` is memoized. If `options` was passed directly then it
+ // would cause the values to be recalculated every time the whole
+ // `options object changed
+ const { keyField, refField, transform } = options ?? {};
+
const [snapshots, loading, error] = useList(query);
const values = useMemo(
() =>
@@ -155,10 +160,5 @@ export const useListVals = <
[snapshots, keyField, refField, transform]
);
- const resArray: ListValsHook = [
- values,
- loading,
- error,
- ];
- return useMemo(() => resArray, resArray);
+ return [values, loading, error];
};
diff --git a/database/useObject.ts b/database/useObject.ts
index 2315854..7346d82 100644
--- a/database/useObject.ts
+++ b/database/useObject.ts
@@ -1,15 +1,13 @@
-import firebase from 'firebase/app';
+import { DataSnapshot, off, onValue, Query } from 'firebase/database';
import { useEffect, useMemo } from 'react';
+import { useIsEqualRef, useLoadingValue } from '../util';
import { snapshotToData, ValOptions } from './helpers';
import { ObjectHook, ObjectValHook, Val } from './types';
-import { useIsEqualRef, useLoadingValue } from '../util';
-export const useObject = (
- query?: firebase.database.Query | null
-): ObjectHook => {
+export const useObject = (query?: Query | null): ObjectHook => {
const { error, loading, reset, setError, setValue, value } = useLoadingValue<
- firebase.database.DataSnapshot,
- firebase.FirebaseError
+ DataSnapshot,
+ Error
>();
const ref = useIsEqualRef(query, reset);
@@ -20,15 +18,14 @@ export const useObject = (
return;
}
- query.on('value', setValue, setError);
+ onValue(query, setValue, setError);
return () => {
- query.off('value', setValue);
+ off(query, 'value', setValue);
};
}, [ref.current]);
- const resArray: ObjectHook = [value, loading, error];
- return useMemo(() => resArray, resArray);
+ return [value, loading, error];
};
export const useObjectVal = <
@@ -36,12 +33,16 @@ export const useObjectVal = <
KeyField extends string = '',
RefField extends string = ''
>(
- query?: firebase.database.Query | null,
+ query?: Query | null,
options?: ValOptions
): ObjectValHook => {
- const keyField = options ? options.keyField : undefined;
- const refField = options ? options.refField : undefined;
- const transform = options ? options.transform : undefined;
+ // Breaking this out in both `useList` and `useObject` rather than
+ // within the `snapshotToData` prevents the developer needing to ensure
+ // that `options` is memoized. If `options` was passed directly then it
+ // would cause the values to be recalculated every time the whole
+ // `options object changed
+ const { keyField, refField, transform } = options ?? {};
+
const [snapshot, loading, error] = useObject(query);
const value = useMemo(
() =>
@@ -51,10 +52,5 @@ export const useObjectVal = <
[snapshot, keyField, refField, transform]
);
- const resArray: ObjectValHook = [
- value,
- loading,
- error,
- ];
- return useMemo(() => resArray, resArray);
+ return [value, loading, error];
};
diff --git a/firebase.json b/firebase.json
new file mode 100644
index 0000000..4711254
--- /dev/null
+++ b/firebase.json
@@ -0,0 +1,18 @@
+{
+ "functions": {
+ "runtime": "node16"
+ },
+ "firestore": {
+ "rules": "firestore.rules",
+ "indexes": "firestore.indexes.json"
+ },
+ "emulators": {
+ "firestore": {
+ "port": 8080
+ },
+ "ui": {
+ "enabled": false
+ },
+ "singleProjectMode": true
+ }
+}
diff --git a/firestore.indexes.json b/firestore.indexes.json
new file mode 100644
index 0000000..4ff4fab
--- /dev/null
+++ b/firestore.indexes.json
@@ -0,0 +1,26 @@
+{
+ // Example:
+ //
+ // "indexes": [
+ // {
+ // "collectionGroup": "widgets",
+ // "queryScope": "COLLECTION",
+ // "fields": [
+ // { "fieldPath": "foo", "arrayConfig": "CONTAINS" },
+ // { "fieldPath": "bar", "mode": "DESCENDING" }
+ // ]
+ // },
+ //
+ // "fieldOverrides": [
+ // {
+ // "collectionGroup": "widgets",
+ // "fieldPath": "baz",
+ // "indexes": [
+ // { "order": "ASCENDING", "queryScope": "COLLECTION" }
+ // ]
+ // },
+ // ]
+ // ]
+ "indexes": [],
+ "fieldOverrides": []
+}
\ No newline at end of file
diff --git a/firestore.rules b/firestore.rules
new file mode 100644
index 0000000..c3c1733
--- /dev/null
+++ b/firestore.rules
@@ -0,0 +1,8 @@
+rules_version = '2';
+service cloud.firestore {
+ match /databases/{database}/documents {
+ match /{document=**} {
+ allow read, write: if true;
+ }
+ }
+}
diff --git a/firestore/README.md b/firestore/README.md
index fc65993..04091cc 100644
--- a/firestore/README.md
+++ b/firestore/README.md
@@ -1,8 +1,6 @@
# React Firebase Hooks - Cloud Firestore
-React Firebase Hooks provides convenience listeners for Collections and Documents stored with
-Cloud Firestore. The hooks wrap around the `firebase.firestore.collection().onSnapshot()`
-and `firebase.firestore().doc().onSnapshot()` methods.
+React Firebase Hooks provides convenience listeners for Collections and Documents stored with Cloud Firestore. The hooks wrap around the `firestore.onSnapshot(...)` method.
In addition to returning the snapshot value, the hooks provide an `error` and `loading` property
to give a complete lifecycle for loading and listening to Cloud Firestore.
@@ -20,14 +18,18 @@ import { useCollection } from 'react-firebase-hooks/firestore';
List of Cloud Firestore hooks:
-- [useCollection](#usecollection)
-- [useCollectionOnce](#usecollectiononce)
-- [useCollectionData](#usecollectiondata)
-- [useCollectionDataOnce](#usecollectiondataonce)
-- [useDocument](#usedocument)
-- [useDocumentOnce](#usedocumentonce)
-- [useDocumentData](#usedocumentdata)
-- [useDocumentDataOnce](#usedocumentdataonce)
+- [React Firebase Hooks - Cloud Firestore](#react-firebase-hooks---cloud-firestore)
+ - [useCollection](#usecollection)
+ - [Full example](#full-example)
+ - [useCollectionOnce](#usecollectiononce)
+ - [useCollectionData](#usecollectiondata)
+ - [useCollectionDataOnce](#usecollectiondataonce)
+ - [useDocument](#usedocument)
+ - [Full example](#full-example-1)
+ - [useDocumentOnce](#usedocumentonce)
+ - [useDocumentData](#usedocumentdata)
+ - [useDocumentDataOnce](#usedocumentdataonce)
+ - [Transforming data](#transforming-data)
Additional functionality:
@@ -41,28 +43,29 @@ const [snapshot, loading, error] = useCollection(query, options);
Retrieve and monitor a collection value in Cloud Firestore.
-Returns a `firebase.firestore.QuerySnapshot` (if a query is specified), a `boolean` to indicate if the data is still being loaded and any `Error` returned by Firebase when trying to load the data.
+Returns a `firestore.QuerySnapshot` (if a query is specified), a `boolean` to indicate if the data is still being loaded and any `firestore.FirestoreError` returned by Firebase when trying to load the data.
The `useCollection` hook takes the following parameters:
-- `query`: (optional) `firebase.firestore.Query` for the data you would like to load
+- `query`: (optional) `firestore.Query` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `snapshotListenOptions`: (optional) `firebase.firestore.SnapshotListenOptions` to customise how the query is loaded
+ - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the query is loaded
Returns:
-- `snapshot`: a `firebase.firestore.QuerySnapshot`, or `undefined` if no query is supplied
+- `snapshot`: a `firestore.QuerySnapshot`, or `undefined` if no query is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error
#### Full example
```js
+import { getFirestore, collection } from 'firebase/firestore';
import { useCollection } from 'react-firebase-hooks/firestore';
const FirestoreCollection = () => {
const [value, loading, error] = useCollection(
- firebase.firestore().collection('hooks'),
+ collection(getFirestore(firebaseApp), 'hooks'),
{
snapshotListenOptions: { includeMetadataChanges: true },
}
@@ -94,68 +97,76 @@ const FirestoreCollection = () => {
const [snapshot, loading, error] = useCollectionOnce(query, options);
```
-Retrieve the current value of the `firebase.firestore.Query`.
+Retrieve the current value of the `firestore.Query`.
The `useCollectionOnce` hook takes the following parameters:
-- `query`: (optional) `firebase.firestore.Query` for the data you would like to load
+- `query`: (optional) `firestore.Query` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `getOptions`: (optional) `firebase.firestore.GetOptions` to customise how the collection is loaded
+ - `getOptions`: (optional) `Object` to customise how the collection is loaded
+ - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cache.
Returns:
-- `snapshot`: a `firebase.firestore.QuerySnapshot`, or `undefined` if no query is supplied
+- `snapshot`: a `firestore.QuerySnapshot`, or `undefined` if no query is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `reload()`: a function that can be called to trigger a reload of the data
### useCollectionData
```js
-const [values, loading, error] = useCollectionData < T > (query, options);
+const [values, loading, error, snapshot] =
+ useCollectionData < T > (query, options);
```
-As `useCollection`, but this hook extracts a typed list of the `firebase.firestore.QuerySnapshot.docs` values, rather than the
-`firebase.firestore.QuerySnapshot` itself.
+As `useCollection`, but this hook extracts a typed list of the `firestore.QuerySnapshot.docs` values, rather than the
+`firestore.QuerySnapshot` itself.
The `useCollectionData` hook takes the following parameters:
-- `query`: (optional) `firebase.firestore.Query` for the data you would like to load
+- `query`: (optional) `firestore.Query` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `idField`: (optional) name of the field that should be populated with the `firebase.firestore.QuerySnapshot.id` property.
- - `refField`: (optional) name of the field that should be populated with the `firebase.firestore.QuerySnapshot.ref` property.
- - `snapshotListenOptions`: (optional) `firebase.firestore.SnapshotListenOptions` to customise how the collection is loaded
- - `snapshotOptions`: (optional) `firebase.firestore.SnapshotOptions` to customise how data is retrieved from snapshots
- - `transform`: (optional) a function that receives the raw `firebase.firestore.DocumentData` for each item in the collection to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below.
+ - `initialValue`: (optional) the initial value returned by the hook, until data from the firestore query has loaded
+ - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the collection is loaded
+ - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots
Returns:
- `values`: an array of `T`, or `undefined` if no query is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `snapshot`: a `firestore.QuerySnapshot`, or `undefined` if no query is supplied. This allows access to the underlying snapshot if needed for any reason, e.g. to view the snapshot metadata
+
+See [Transforming data](#transforming-data) for how to transform data as it leaves Firestore and access the underlying `id` and `ref` fields of the snapshot.
### useCollectionDataOnce
```js
-const [values, loading, error] = useCollectionDataOnce < T > (query, options);
+const [values, loading, error, snapshot] =
+ useCollectionDataOnce < T > (query, options);
```
-As `useCollectionData`, but this hook will only read the current value of the `firebase.firestore.Query`.
+As `useCollectionData`, but this hook will only read the current value of the `firestore.Query`.
The `useCollectionDataOnce` hook takes the following parameters:
-- `query`: (optional) `firebase.firestore.Query` for the data you would like to load
+- `query`: (optional) `firestore.Query` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `getOptions`: (optional) `firebase.firestore.GetOptions` to customise how the collection is loaded
- - `idField`: (optional) name of the field that should be populated with the `firebase.firestore.QuerySnapshot.id` property.
- - `refField`: (optional) name of the field that should be populated with the `firebase.firestore.QuerySnapshot.ref` property.
- - `snapshotOptions`: (optional) `firebase.firestore.SnapshotOptions` to customise how data is retrieved from snapshots
- - `transform`: (optional) a function that receives the raw `firebase.firestore.DocumentData` for each item in the collection to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below.
+ - `getOptions`: (optional) `Object` to customise how the collection is loaded
+ - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cache.
+ - `initialValue`: (optional) the initial value returned by the hook, until data from the firestore query has loaded
+ - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots
Returns:
- `values`: an array of `T`, or `undefined` if no query is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `snapshot`: a `firestore.QuerySnapshot`, or `undefined` if no query is supplied. This allows access to the underlying snapshot if needed for any reason, e.g. to view the snapshot metadata
+- `reload()`: a function that can be called to trigger a reload of the data
+
+See [Transforming data](#transforming-data) for how to transform data as it leaves Firestore and access the underlying `id` and `ref` fields of the snapshot.
### useDocument
@@ -167,24 +178,25 @@ Retrieve and monitor a document value in Cloud Firestore.
The `useDocument` hook takes the following parameters:
-- `reference`: (optional) `firebase.firestore.DocumentReference` for the data you would like to load
+- `reference`: (optional) `firestore.DocumentReference` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `snapshotListenOptions`: (optional) `firebase.firestore.SnapshotListenOptions` to customise how the query is loaded
+ - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the query is loaded
Returns:
-- `snapshot`: a `firebase.firestore.DocumentSnapshot`, or `undefined` if no query is supplied
+- `snapshot`: a `firestore.DocumentSnapshot`, or `undefined` if no query is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error
#### Full example
```js
+import { getFirestore, doc } from 'firebase/firestore';
import { useDocument } from 'react-firebase-hooks/firestore';
const FirestoreDocument = () => {
const [value, loading, error] = useDocument(
- firebase.firestore().doc('hooks/nBShXiRGFAhuiPfBaGpt'),
+ doc(getFirestore(firebaseApp), 'hooks', 'nBShXiRGFAhuiPfBaGpt'),
{
snapshotListenOptions: { includeMetadataChanges: true },
}
@@ -204,82 +216,114 @@ const FirestoreDocument = () => {
### useDocumentOnce
```js
-const [snapshot, loading, error] = useDocumentOnce(reference, options);
+const [snapshot, loading, error, reload] = useDocumentOnce(reference, options);
```
-Retrieve the current value of the `firebase.firestore.DocumentReference`.
+Retrieve the current value of the `firestore.DocumentReference`.
The `useDocumentOnce` hook takes the following parameters:
-- `reference`: (optional) `firebase.firestore.DocumentReference` for the data you would like to load
+- `reference`: (optional) `firestore.DocumentReference` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `getOptions`: (optional) `firebase.firestore.GetOptions` to customise how the collection is loaded
+ - `getOptions`: (optional) `Object` to customise how the collection is loaded
+ - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cache.
Returns:
-- `snapshot`: a `firebase.firestore.DocumentSnapshot`, or `undefined` if no reference is supplied
+- `snapshot`: a `firestore.DocumentSnapshot`, or `undefined` if no reference is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `reload()`: a function that can be called to trigger a reload of the data
### useDocumentData
```js
-const [value, loading, error] = useDocumentData < T > (reference, options);
+const [value, loading, error, snapshot] =
+ useDocumentData < T > (reference, options);
```
-As `useDocument`, but this hook extracts the typed contents of `firebase.firestore.DocumentSnapshot.val()`, rather than the
-`firebase.firestore.DocumentSnapshot` itself.
+As `useDocument`, but this hook extracts the typed contents of `firestore.DocumentSnapshot.data()`, rather than the
+`firestore.DocumentSnapshot` itself.
The `useDocumentData` hook takes the following parameters:
-- `reference`: (optional) `firebase.firestore.DocumentReference` for the data you would like to load
+- `reference`: (optional) `firestore.DocumentReference` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `idField`: (optional) name of the field that should be populated with the `firebase.firestore.DocumentSnapshot.id` property.
- - `refField`: (optional) name of the field that should be populated with the `firebase.firestore.QuerySnapshot.ref` property.
- - `snapshotListenOptions`: (optional) `firebase.firestore.SnapshotListenOptions` to customise how the collection is loaded
- - `snapshotOptions`: (optional) `firebase.firestore.SnapshotOptions` to customise how data is retrieved from snapshots
- - `transform`: (optional) a function that receives the raw `firebase.firestore.DocumentData` to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below.
+ - `initialValue`: (optional) the initial value returned by the hook, until data from the firestore query has loaded
+ - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the collection is loaded
+ - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots
Returns:
- `value`: `T`, or `undefined` if no query is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `snapshot`: a `firestore.DocumentSnapshot`, or `undefined` if no query is supplied. This allows access to the underlying snapshot if needed for any reason, e.g. to view the snapshot metadata
+
+See [Transforming data](#transforming-data) for how to transform data as it leaves Firestore and access the underlying `id` and `ref` fields of the snapshot.
### useDocumentDataOnce
```js
-const [value, loading, error] = useDocumentDataOnce < T > (reference, options);
+const [value, loading, error, snapshot, reload] =
+ useDocumentDataOnce < T > (reference, options);
```
-As `useDocument`, but this hook will only read the current value of the `firebase.firestore.DocumentReference`.
+As `useDocument`, but this hook will only read the current value of the `firestore.DocumentReference`.
The `useDocumentDataOnce` hook takes the following parameters:
-- `reference`: (optional) `firebase.firestore.DocumentReference` for the data you would like to load
+- `reference`: (optional) `firestore.DocumentReference` for the data you would like to load
- `options`: (optional) `Object` with the following parameters:
- - `getOptions`: (optional) `firebase.firestore.GetOptions` to customise how the collection is loaded
- - `idField`: (optional) name of the field that should be populated with the `firebase.firestore.DocumentSnapshot.id` property.
- - `refField`: (optional) name of the field that should be populated with the `firebase.firestore.QuerySnapshot.ref` property.
- - `snapshotOptions`: (optional) `firebase.firestore.SnapshotOptions` to customise how data is retrieved from snapshots
- - `transform`: (optional) a function that receives the raw `firebase.firestore.DocumentData` to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below.
+ - `getOptions`: (optional) `Object` to customise how the collection is loaded
+ - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cache
+ - `initialValue`: (optional) the initial value returned by the hook, until data from the firestore query has loaded
+ - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots
Returns:
- `value`: `T`, or `undefined` if no query is supplied
- `loading`: a `boolean` to indicate if the data is still being loaded
-- `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error
+- `snapshot`: a `firestore.DocumentSnapshot`, or `undefined` if no query is supplied. This allows access to the underlying snapshot if needed for any reason, e.g. to view the snapshot metadata
+- `reload()`: a function that can be called to trigger a reload of the data
+
+See [Transforming data](#transforming-data) for how to transform data as it leaves Firestore and access the underlying `id` and `ref` fields of the snapshot.
## Transforming data
-Firestore allows a restricted number of data types in its store, which may not be flexible enough for your application. Both `useCollectionData` and `useDocumentData` support an optional `transform` function which allows the transformation of the underlying Firestore data into whatever format the application requires, e.g. a `Date` type.
+Firestore allows a restricted number of data types in its store, which may not be flexible enough for your application. As of Firebase 9, there is a built in FirestoreDataConverter which allows you to transform data as it leaves the Firestore database, as well as access the `id` and `ref` fields of the underlying snapshot. This is described here: https://firebase.google.com/docs/reference/js/firestore_.firestoredataconverter
-```js
-transform?: (val: any) => T;
-```
+> NOTE: This replaces the `transform`, `idField` and `refField` options that were available in `react-firebase-hooks` v4 and earlier.
-The `transform` function is passed a single row of a data, so will be called once when used with `useDocumentData` and multiple times when used with `useCollectionData`.
+### Example
-The `transform` function will not receive the `id` or `ref` values referenced in the properties named in the `idField` or `refField` options, nor it is expected to produce them. Either or both, if specified, will be merged afterwards.
+```js
+type Post = {
+ author: string,
+ id: string,
+ ref: DocumentReference,
+ title: string,
+};
-If the `transform` function is defined within your React component, it is recomended that you memoize the function to prevent unnecessry renders.
+const postConverter: FirestoreDataConverter = {
+ toFirestore(post: WithFieldValue): DocumentData {
+ return { author: post.author, title: post.title };
+ },
+ fromFirestore(
+ snapshot: QueryDocumentSnapshot,
+ options: SnapshotOptions
+ ): Post {
+ const data = snapshot.data(options);
+ return {
+ author: data.author,
+ id: snapshot.id,
+ ref: snapshot.ref,
+ title: data.title,
+ };
+ },
+};
+
+const ref = collection(firestore, 'posts').withConverter(postConverter);
+const [data, loading, error] = useCollectionData(ref);
+```
diff --git a/firestore/helpers/index.ts b/firestore/helpers/index.ts
index 014eedb..d803d9f 100644
--- a/firestore/helpers/index.ts
+++ b/firestore/helpers/index.ts
@@ -1,21 +1,44 @@
-import firebase from 'firebase/app';
+import {
+ CollectionReference,
+ DocumentReference,
+ Query,
+ queryEqual,
+ refEqual,
+} from 'firebase/firestore';
+import { RefHook, useComparatorRef } from '../../util';
-export const snapshotToData = (
- snapshot: firebase.firestore.DocumentSnapshot,
- snapshotOptions?: firebase.firestore.SnapshotOptions,
- idField?: string,
- refField?: string,
- transform?: (val: any) => T
-) => {
- if (!snapshot.exists) {
- return undefined;
- }
+const isRefEqual = <
+ T extends DocumentReference | CollectionReference
+>(
+ v1: T | null | undefined,
+ v2: T | null | undefined
+): boolean => {
+ const bothNull: boolean = !v1 && !v2;
+ const equal: boolean = !!v1 && !!v2 && refEqual(v1, v2);
+ return bothNull || equal;
+};
+
+export const useIsFirestoreRefEqual = <
+ T extends DocumentReference | CollectionReference
+>(
+ value: T | null | undefined,
+ onChange?: () => void
+): RefHook => {
+ return useComparatorRef(value, isRefEqual, onChange);
+};
+
+const isQueryEqual = >(
+ v1: T | null | undefined,
+ v2: T | null | undefined
+): boolean => {
+ const bothNull: boolean = !v1 && !v2;
+ const equal: boolean = !!v1 && !!v2 && queryEqual(v1, v2);
+ return bothNull || equal;
+};
- return {
- ...(transform
- ? transform(snapshot.data(snapshotOptions))
- : snapshot.data(snapshotOptions)),
- ...(idField ? { [idField]: snapshot.id } : null),
- ...(refField ? { [refField]: snapshot.ref } : null),
- };
+export const useIsFirestoreQueryEqual = >(
+ value: T | null | undefined,
+ onChange?: () => void
+): RefHook => {
+ return useComparatorRef(value, isQueryEqual, onChange);
};
diff --git a/firestore/index.js.flow b/firestore/index.js.flow
deleted file mode 100644
index 0ff6164..0000000
--- a/firestore/index.js.flow
+++ /dev/null
@@ -1,73 +0,0 @@
-// @flow
-import type {
- DocumentReference,
- DocumentSnapshot,
- GetOptions,
- Query,
- QuerySnapshot,
- SnapshotListenOptions,
-} from 'firebase/firestore';
-
-type LoadingHook = [T | void, boolean, Error | void];
-
-export type CollectionHook = LoadingHook;
-export type CollectionDataHook = LoadingHook;
-export type DocumentHook = LoadingHook;
-export type DocumentDataHook = LoadingHook;
-
-declare export function useCollection(
- query?: Query | null,
- options?: {
- snapshotListenOptions?: SnapshotListenOptions,
- }
-): CollectionHook;
-declare export function useCollectionOnce(
- query?: Query | null,
- options?: {
- getOptions?: GetOptions,
- }
-): CollectionHook;
-declare export function useCollectionData(
- query?: Query | null,
- options?: {
- idField?: string,
- refField?: string,
- snapshotListenOptions?: SnapshotListenOptions,
- }
-): CollectionDataHook;
-declare export function useCollectionDataOnce(
- query?: Query | null,
- options?: {
- getOptions?: GetOptions,
- idField?: string,
- refField?: string,
- }
-): CollectionDataHook;
-declare export function useDocument(
- ref?: DocumentReference | null,
- options?: {
- snapshotListenOptions?: SnapshotListenOptions,
- }
-): DocumentHook;
-declare export function useDocumentOnce(
- ref?: DocumentReference | null,
- options?: {
- getOptions?: GetOptions,
- }
-): DocumentHook;
-declare export function useDocumentData(
- ref?: DocumentReference | null,
- options?: {
- idField?: string,
- refField?: string,
- snapshotListenOptions?: SnapshotListenOptions,
- }
-): DocumentDataHook;
-declare export function useDocumentDataOnce(
- ref?: DocumentReference | null,
- options?: {
- getOptions?: GetOptions,
- idField?: string,
- refField?: string,
- }
-): DocumentDataHook;
diff --git a/firestore/types.ts b/firestore/types.ts
index 1962007..e28ab41 100644
--- a/firestore/types.ts
+++ b/firestore/types.ts
@@ -1,42 +1,61 @@
-import firebase from 'firebase/app';
+import {
+ DocumentData,
+ DocumentSnapshot,
+ FirestoreError,
+ QuerySnapshot,
+ SnapshotListenOptions,
+ SnapshotOptions,
+} from 'firebase/firestore';
import { LoadingHook } from '../util';
-type IDOptions = {
- idField?: string;
- refField?: string;
- snapshotOptions?: firebase.firestore.SnapshotOptions;
- transform?: (val: any) => T;
+export type IDOptions = {
+ snapshotOptions?: SnapshotOptions;
};
export type Options = {
- snapshotListenOptions?: firebase.firestore.SnapshotListenOptions;
+ snapshotListenOptions?: SnapshotListenOptions;
+};
+export type InitialValueOptions = {
+ initialValue?: T;
};
export type DataOptions = Options & IDOptions;
export type OnceOptions = {
- getOptions?: firebase.firestore.GetOptions;
+ getOptions?: GetOptions;
+};
+export type GetOptions = {
+ source?: 'default' | 'server' | 'cache';
};
export type OnceDataOptions = OnceOptions & IDOptions;
-export type Data<
- T = firebase.firestore.DocumentData,
- IDField extends string = '',
- RefField extends string = ''
-> = T & Record & Record;
-export type CollectionHook = LoadingHook<
- firebase.firestore.QuerySnapshot,
- firebase.FirebaseError
+export type CollectionHook = LoadingHook<
+ QuerySnapshot,
+ FirestoreError
>;
-export type CollectionDataHook<
- T = firebase.firestore.DocumentData,
- IDField extends string = '',
- RefField extends string = ''
-> = LoadingHook[], firebase.FirebaseError>;
+export type CollectionOnceHook = [
+ ...CollectionHook,
+ () => Promise
+];
+export type CollectionDataHook = [
+ ...LoadingHook,
+ QuerySnapshot | undefined
+];
+export type CollectionDataOnceHook = [
+ ...CollectionDataHook,
+ () => Promise
+];
-export type DocumentHook = LoadingHook<
- firebase.firestore.DocumentSnapshot,
- firebase.FirebaseError
+export type DocumentHook = LoadingHook<
+ DocumentSnapshot,
+ FirestoreError
>;
-export type DocumentDataHook<
- T = firebase.firestore.DocumentData,
- IDField extends string = '',
- RefField extends string = ''
-> = LoadingHook, firebase.FirebaseError>;
+export type DocumentOnceHook = [
+ ...DocumentHook,
+ () => Promise
+];
+export type DocumentDataHook = [
+ ...LoadingHook,
+ DocumentSnapshot | undefined
+];
+export type DocumentDataOnceHook = [
+ ...DocumentDataHook,
+ () => Promise
+];
diff --git a/firestore/useCollection.ts b/firestore/useCollection.ts
index ccae055..48084d0 100644
--- a/firestore/useCollection.ts
+++ b/firestore/useCollection.ts
@@ -1,136 +1,165 @@
-import firebase from 'firebase/app';
-import { useEffect, useMemo } from 'react';
-import { snapshotToData } from './helpers';
import {
- CollectionHook,
+ DocumentData,
+ FirestoreError,
+ getDocs,
+ getDocsFromCache,
+ getDocsFromServer,
+ onSnapshot,
+ Query,
+ QuerySnapshot,
+ SnapshotOptions,
+} from 'firebase/firestore';
+import { useCallback, useEffect, useMemo } from 'react';
+import { useLoadingValue } from '../util';
+import useIsMounted from '../util/useIsMounted';
+import { useIsFirestoreQueryEqual } from './helpers';
+import {
CollectionDataHook,
- Data,
+ CollectionDataOnceHook,
+ CollectionHook,
+ CollectionOnceHook,
DataOptions,
- OnceOptions,
+ GetOptions,
+ InitialValueOptions,
OnceDataOptions,
+ OnceOptions,
Options,
} from './types';
-import { useIsEqualRef, useLoadingValue } from '../util';
-export const useCollection = (
- query?: firebase.firestore.Query | null,
+export const useCollection = (
+ query?: Query | null,
options?: Options
): CollectionHook => {
- return useCollectionInternal(true, query, options);
-};
+ const { error, loading, reset, setError, setValue, value } = useLoadingValue<
+ QuerySnapshot,
+ FirestoreError
+ >();
+ const ref = useIsFirestoreQueryEqual>(query, reset);
-export const useCollectionOnce = (
- query?: firebase.firestore.Query | null,
- options?: OnceOptions
-): CollectionHook => {
- return useCollectionInternal(false, query, options);
-};
+ useEffect(() => {
+ if (!ref.current) {
+ setValue(undefined);
+ return;
+ }
+ const unsubscribe = options?.snapshotListenOptions
+ ? onSnapshot(
+ ref.current,
+ options.snapshotListenOptions,
+ setValue,
+ setError
+ )
+ : onSnapshot(ref.current, setValue, setError);
-export const useCollectionData = <
- T = firebase.firestore.DocumentData,
- IDField extends string = '',
- RefField extends string = ''
->(
- query?: firebase.firestore.Query | null,
- options?: DataOptions
-): CollectionDataHook => {
- return useCollectionDataInternal(true, query, options);
-};
+ return () => {
+ unsubscribe();
+ };
+ }, [ref.current]);
-export const useCollectionDataOnce = <
- T = firebase.firestore.DocumentData,
- IDField extends string = '',
- RefField extends string = ''
->(
- query?: firebase.firestore.Query | null,
- options?: OnceDataOptions
-): CollectionDataHook => {
- return useCollectionDataInternal(false, query, options);
+ return [value as QuerySnapshot, loading, error];
};
-const useCollectionInternal = (
- listen: boolean,
- query?: firebase.firestore.Query | null,
- options?: Options & OnceOptions
-) => {
+export const useCollectionOnce = (
+ query?: Query | null,
+ options?: OnceOptions
+): CollectionOnceHook => {
const { error, loading, reset, setError, setValue, value } = useLoadingValue<
- firebase.firestore.QuerySnapshot,
- firebase.FirebaseError
+ QuerySnapshot,
+ FirestoreError
>();
- const ref = useIsEqualRef(query, reset);
+ const isMounted = useIsMounted();
+ const ref = useIsFirestoreQueryEqual>(query, reset);
+
+ const loadData = useCallback(
+ async (query?: Query | null, options?: Options & OnceOptions) => {
+ if (!query) {
+ setValue(undefined);
+ return;
+ }
+ const get = getDocsFnFromGetOptions(options?.getOptions);
+
+ try {
+ const result = await get(query);
+ if (isMounted) {
+ setValue(result);
+ }
+ } catch (error) {
+ if (isMounted) {
+ setError(error as FirestoreError);
+ }
+ }
+ },
+ []
+ );
+
+ const reloadData = useCallback(() => loadData(ref.current, options), [
+ loadData,
+ ref.current,
+ ]);
useEffect(() => {
- if (!ref.current) {
- setValue(undefined);
- return;
- }
- if (listen) {
- const listener =
- options && options.snapshotListenOptions
- ? ref.current.onSnapshot(
- options.snapshotListenOptions,
- setValue,
- setError
- )
- : ref.current.onSnapshot(setValue, setError);
-
- return () => {
- listener();
- };
- } else {
- ref.current
- .get(options ? options.getOptions : undefined)
- .then(setValue)
- .catch(setError);
- }
+ loadData(ref.current, options);
}, [ref.current]);
- const resArray: CollectionHook = [
- value as firebase.firestore.QuerySnapshot,
- loading,
- error,
- ];
- return useMemo(() => resArray, resArray);
+ return [value as QuerySnapshot, loading, error, reloadData];
+};
+
+export const useCollectionData = (
+ query?: Query | null,
+ options?: DataOptions & InitialValueOptions
+): CollectionDataHook => {
+ const [snapshots, loading, error] = useCollection(query, options);
+
+ const values = getValuesFromSnapshots(
+ snapshots,
+ options?.snapshotOptions,
+ options?.initialValue
+ );
+
+ return [values, loading, error, snapshots];
};
-const useCollectionDataInternal = <
- T = firebase.firestore.DocumentData,
- IDField extends string = '',
- RefField extends string = ''
->(
- listen: boolean,
- query?: firebase.firestore.Query | null,
- options?: DataOptions & OnceDataOptions
-): CollectionDataHook => {
- const idField = options ? options.idField : undefined;
- const refField = options ? options.refField : undefined;
- const snapshotOptions = options ? options.snapshotOptions : undefined;
- const transform = options ? options.transform : undefined;
- const [snapshots, loading, error] = useCollectionInternal(
- listen,
+export const useCollectionDataOnce = (
+ query?: Query | null,
+ options?: OnceDataOptions & InitialValueOptions
+): CollectionDataOnceHook => {
+ const [snapshots, loading, error, reloadData] = useCollectionOnce(
query,
options
);
- const values = useMemo(
+
+ const values = getValuesFromSnapshots(
+ snapshots,
+ options?.snapshotOptions,
+ options?.initialValue
+ );
+
+ return [values, loading, error, snapshots, reloadData];
+};
+
+const getValuesFromSnapshots = (
+ snapshots: QuerySnapshot | undefined,
+ options?: SnapshotOptions,
+ initialValue?: T[]
+): T[] | undefined => {
+ return useMemo(
() =>
- (snapshots
- ? snapshots.docs.map((doc) =>
- snapshotToData(
- doc,
- snapshotOptions,
- idField,
- refField,
- transform
- )
- )
- : undefined) as Data[],
- [snapshots, snapshotOptions, idField, refField, transform]
+ (snapshots?.docs.map((doc) => doc.data(options)) ?? initialValue) as
+ | T[]
+ | undefined,
+ [snapshots, options]
);
+};
- const resArray: CollectionDataHook = [
- values,
- loading,
- error,
- ];
- return useMemo(() => resArray, resArray);
+const getDocsFnFromGetOptions = (
+ { source }: GetOptions = { source: 'default' }
+) => {
+ switch (source) {
+ default:
+ case 'default':
+ return getDocs;
+ case 'cache':
+ return getDocsFromCache;
+ case 'server':
+ return getDocsFromServer;
+ }
};
diff --git a/firestore/useDocument.test.ts b/firestore/useDocument.test.ts
new file mode 100644
index 0000000..cfed328
--- /dev/null
+++ b/firestore/useDocument.test.ts
@@ -0,0 +1,40 @@
+import { addDoc, collection, doc } from 'firebase/firestore';
+
+import { db } from '../test/firebase';
+import { renderHook } from '@testing-library/react-hooks';
+import { useDocument } from './useDocument';
+
+describe('useDocument hook', () => {
+ test('begins in loading state', async () => {
+ // arrange
+ const { id } = await addDoc(collection(db, 'test'), {});
+
+ // act
+ const { result, unmount } = renderHook(() =>
+ useDocument(doc(collection(db, 'test'), id))
+ );
+
+ //assert
+ expect(result.current[1]).toBeTruthy();
+
+ // clean up
+ unmount();
+ });
+
+ test('loads and returns data from server', async () => {
+ // arrange
+ const { id } = await addDoc(collection(db, 'test'), { name: 'bo' });
+
+ // act
+ const { result, waitFor, unmount } = renderHook(() =>
+ useDocument(doc(collection(db, 'test'), id))
+ );
+ await waitFor(() => result.current[1] === false);
+
+ // assert
+ expect(result.current[0]?.data()).toEqual({ name: 'bo' });
+
+ // clean up
+ unmount();
+ });
+});
diff --git a/firestore/useDocument.ts b/firestore/useDocument.ts
index f987a27..cfd8645 100644
--- a/firestore/useDocument.ts
+++ b/firestore/useDocument.ts
@@ -1,134 +1,167 @@
-import firebase from 'firebase/app';
-import { useEffect, useMemo } from 'react';
-import { snapshotToData } from './helpers';
import {
- Data,
+ DocumentData,
+ DocumentReference,
+ DocumentSnapshot,
+ FirestoreError,
+ getDoc,
+ getDocFromCache,
+ getDocFromServer,
+ onSnapshot,
+ SnapshotOptions,
+} from 'firebase/firestore';
+import { useCallback, useEffect, useMemo } from 'react';
+import { useLoadingValue } from '../util';
+import useIsMounted from '../util/useIsMounted';
+import { useIsFirestoreRefEqual } from './helpers';
+import {
DataOptions,
- DocumentHook,
DocumentDataHook,
- OnceOptions,
+ DocumentDataOnceHook,
+ DocumentHook,
+ DocumentOnceHook,
+ GetOptions,
+ InitialValueOptions,
OnceDataOptions,
+ OnceOptions,
Options,
} from './types';
-import { useIsEqualRef, useLoadingValue } from '../util';
-export const useDocument = (
- docRef?: firebase.firestore.DocumentReference | null,
+export const useDocument = (
+ docRef?: DocumentReference | null,
options?: Options
): DocumentHook => {
- return useDocumentInternal(true, docRef, options);
-};
+ const { error, loading, reset, setError, setValue, value } = useLoadingValue<
+ DocumentSnapshot,
+ FirestoreError
+ >();
+ const ref = useIsFirestoreRefEqual>(docRef, reset);
-export const useDocumentOnce = (
- docRef?: firebase.firestore.DocumentReference | null,
- options?: OnceOptions
-): DocumentHook => {
- return useDocumentInternal(false, docRef, options);
-};
+ useEffect(() => {
+ if (!ref.current) {
+ setValue(undefined);
+ return;
+ }
+ const unsubscribe = options?.snapshotListenOptions
+ ? onSnapshot(
+ ref.current,
+ options.snapshotListenOptions,
+ setValue,
+ setError
+ )
+ : onSnapshot(ref.current, setValue, setError);
-export const useDocumentData = <
- T = firebase.firestore.DocumentData,
- IDField extends string = '',
- RefField extends string = ''
->(
- docRef?: firebase.firestore.DocumentReference | null,
- options?: DataOptions
-): DocumentDataHook => {
- return useDocumentDataInternal(true, docRef, options);
-};
+ return () => {
+ unsubscribe();
+ };
+ }, [ref.current]);
-export const useDocumentDataOnce = <
- T = firebase.firestore.DocumentData,
- IDField extends string = '',
- RefField extends string = ''
->(
- docRef?: firebase.firestore.DocumentReference | null,
- options?: OnceDataOptions
-): DocumentDataHook => {
- return useDocumentDataInternal(false, docRef, options);
+ return [value as DocumentSnapshot, loading, error];
};
-const useDocumentInternal = (
- listen: boolean,
- docRef?: firebase.firestore.DocumentReference | null,
- options?: Options & OnceOptions
-): DocumentHook => {
+export const useDocumentOnce = (
+ docRef?: DocumentReference | null,
+ options?: OnceOptions
+): DocumentOnceHook => {
const { error, loading, reset, setError, setValue, value } = useLoadingValue<
- firebase.firestore.DocumentSnapshot,
- firebase.FirebaseError
+ DocumentSnapshot,
+ FirestoreError
>();
- const ref = useIsEqualRef(docRef, reset);
+ const isMounted = useIsMounted();
+ const ref = useIsFirestoreRefEqual>(docRef, reset);
+
+ const loadData = useCallback(
+ async (reference?: DocumentReference