├── .editorconfig ├── .gitignore ├── .opensource └── project.json ├── LICENSE ├── README.md ├── auth ├── README.md ├── index.ts ├── package.json ├── types.ts ├── useAuthState.ts ├── useCreateUserWithEmailAndPassword.ts ├── useSendEmailVerification.ts ├── useSendPasswordResetEmail.ts ├── useSignInWithEmailAndPassword.ts ├── useSignInWithPopup.ts └── useUpdateUser.ts ├── database ├── README.md ├── helpers │ ├── index.ts │ └── useListReducer.ts ├── index.ts ├── package.json ├── types.ts ├── useList.ts └── useObject.ts ├── firestore ├── README.md ├── helpers │ └── index.ts ├── index.ts ├── package.json ├── types.ts ├── useCollection.ts └── useDocument.ts ├── functions ├── README.md ├── index.ts ├── package.json └── useHttpsCallable.ts ├── messaging ├── README.md ├── index.ts ├── package.json └── useToken.ts ├── package-lock.json ├── package.json ├── prettier.config.js ├── rollup.config.js ├── storage ├── README.md ├── index.ts ├── package.json ├── useDownloadURL.ts └── useUploadFile.ts ├── tsconfig.json └── util ├── index.ts ├── package.json ├── refHooks.ts └── useLoadingValue.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NPM / Yarn 2 | .rpt2_cache 3 | node_modules 4 | npm-debug.log 5 | 6 | # Rollup build output 7 | **/dist 8 | **/*.d.ts 9 | 10 | # OS files 11 | .DS_Store 12 | 13 | # Testing output 14 | .nyc_output 15 | coverage 16 | 17 | #test files 18 | /example 19 | -------------------------------------------------------------------------------- /.opensource/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React Firebase Hooks", 3 | "platforms": ["Web"], 4 | "content": "README.md", 5 | "pages": [], 6 | "related": ["firebase/firebase-js-sdk"], 7 | "tabs": [] 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2018 CS Frequency Ltd. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Firebase Hooks 2 | 3 | A set of reusable [React Hooks](https://reactjs.org/docs/hooks-intro.html) for [Firebase](https://firebase.google.com/). 4 | 5 | [![npm version](https://img.shields.io/npm/v/react-firebase-hooks.svg?style=flat-square)](https://www.npmjs.com/package/react-firebase-hooks) 6 | [![npm downloads](https://img.shields.io/npm/dm/react-firebase-hooks.svg?style=flat-square)](https://www.npmjs.com/package/react-firebase-hooks) 7 | 8 | This documentation is for v5 of React Firebase Hooks which requires Firebase v9 or higher. 9 | 10 | - For v4 documentation (Firebase v9), see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2). 11 | - For v3 documentation (Firebase v8), see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v3.0.4). 12 | - For v2 documentation, see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v2.2.0). 13 | 14 | ## Installation 15 | 16 | React Firebase Hooks v4 requires **React 16.8.0 or later** and **Firebase v9.0.0 or later**. 17 | 18 | > 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. 19 | 20 | ```bash 21 | # with npm 22 | npm install --save react-firebase-hooks 23 | 24 | # with yarn 25 | yarn add react-firebase-hooks 26 | ``` 27 | 28 | 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. 29 | 30 | ## Why? 31 | 32 | 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. 33 | 34 | ## Upgrading from v4 to v5 35 | 36 | 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. 37 | 38 | ## Documentation 39 | 40 | - [Authentication Hooks](/auth) 41 | - [Cloud Firestore Hooks](/firestore) 42 | - [Cloud Functions Hooks](/functions) 43 | - [Cloud Messaging Hooks](/messaging) 44 | - [Cloud Storage Hooks](/storage) 45 | - [Realtime Database Hooks](/database) 46 | 47 | ## License 48 | 49 | - See [LICENSE](/LICENSE) 50 | -------------------------------------------------------------------------------- /auth/README.md: -------------------------------------------------------------------------------- 1 | # React Firebase Hooks - Auth 2 | 3 | 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. 4 | 5 | All hooks can be imported from `react-firebase-hooks/auth`, e.g. 6 | 7 | ```js 8 | import { useAuthState } from 'react-firebase-hooks/auth'; 9 | ``` 10 | 11 | List of Auth hooks: 12 | 13 | - [useAuthState](#useauthstate) 14 | - [useCreateUserWithEmailAndPassword](#usecreateuserwithemailandpassword) 15 | - [useSignInWithEmailAndPassword](#usesigninwithemailandpassword) 16 | - [useSignInWithApple](#usesigninwithapple) 17 | - [useSignInWithFacebook](#usesigninwithfacebook) 18 | - [useSignInWithGithub](#usesigninwithgithub) 19 | - [useSignInWithGoogle](#usesigninwithgoogle) 20 | - [useSignInWithMicrosoft](#usesigninwithmicrosoft) 21 | - [useSignInWithTwitter](#usesigninwithtwitter) 22 | - [useSignInWithYahoo](#usesigninwithyahoo) 23 | - [useUpdateEmail](#useupdateemail) 24 | - [useUpdatePassword](#useupdatepassword) 25 | - [useUpdateProfile](#useupdateprofile) 26 | - [useSendPasswordResetEmail](#usesendpasswordresetemail) 27 | - [useSendEmailVerification](#usesendemailverification) 28 | 29 | ### useAuthState 30 | 31 | ```js 32 | const [user, loading, error] = useAuthState(auth, options); 33 | ``` 34 | 35 | Retrieve and monitor the authentication state from Firebase. 36 | 37 | The `useAuthState` hook takes the following parameters: 38 | 39 | - `auth`: `auth.Auth` instance for the app you would like to monitor 40 | - `options`: (optional) `Object with the following parameters: 41 | - `onUserChanged`: (optional) function to be called with `auth.User` each time the user changes. This allows you to do things like load custom claims. 42 | 43 | Returns: 44 | 45 | - `user`: The `auth.User` if logged in, or `null` if not 46 | - `loading`: A `boolean` to indicate whether the the authentication state is still being loaded 47 | - `error`: Any `AuthError` returned by Firebase when trying to load the user, or `undefined` if there is no error 48 | 49 | #### If you are registering or signing in the user for the first time consider using [useCreateUserWithEmailAndPassword](#usecreateuserwithemailandpassword), [useSignInWithEmailAndPassword](#usesigninwithemailandpassword) 50 | 51 | #### Full Example 52 | 53 | ```js 54 | import { getAuth, signInWithEmailAndPassword, signOut } from 'firebase/auth'; 55 | import { useAuthState } from 'react-firebase-hooks/auth'; 56 | 57 | const auth = getAuth(firebaseApp); 58 | 59 | const login = () => { 60 | signInWithEmailAndPassword(auth, 'test@test.com', 'password'); 61 | }; 62 | const logout = () => { 63 | signOut(auth); 64 | }; 65 | 66 | const CurrentUser = () => { 67 | const [user, loading, error] = useAuthState(auth); 68 | 69 | if (loading) { 70 | return ( 71 |
72 |

Initialising User...

73 |
74 | ); 75 | } 76 | if (error) { 77 | return ( 78 |
79 |

Error: {error}

80 |
81 | ); 82 | } 83 | if (user) { 84 | return ( 85 |
86 |

Current User: {user.email}

87 | 88 |
89 | ); 90 | } 91 | return ; 92 | }; 93 | ``` 94 | 95 | ### useCreateUserWithEmailAndPassword 96 | 97 | ```js 98 | const [ 99 | createUserWithEmailAndPassword, 100 | user, 101 | loading, 102 | error, 103 | ] = useCreateUserWithEmailAndPassword(auth); 104 | ``` 105 | 106 | Create a user with email and password. Wraps the underlying `firebase.auth().createUserWithEmailAndPassword` method and provides additional `loading` and `error` information. 107 | 108 | The `useCreateUserWithEmailAndPassword` hook takes the following parameters: 109 | 110 | - `auth`: `auth.Auth` instance for the app you would like to monitor 111 | - `options`: (optional) `Object` with the following parameters: 112 | - `emailVerificationOptions`: (optional) `auth.ActionCodeSettings` to customise the email verification 113 | - `sendEmailVerification`: (optional) `boolean` to trigger sending of an email verification after the user has been created 114 | 115 | Returns: 116 | 117 | - `createUserWithEmailAndPassword(email: string, password: string)`: a function you can call to start the registration 118 | - `user`: The `User` if the user was created or `undefined` if not 119 | - `loading`: A `boolean` to indicate whether the the user creation is processing 120 | - `error`: Any `Error` returned by Firebase when trying to create the user, or `undefined` if there is no error 121 | 122 | #### Full Example 123 | 124 | ```jsx 125 | import { useCreateUserWithEmailAndPassword } from 'react-firebase-hooks/auth'; 126 | 127 | const SignIn = () => { 128 | const [email, setEmail] = useState(''); 129 | const [password, setPassword] = useState(''); 130 | const [ 131 | createUserWithEmailAndPassword, 132 | user, 133 | loading, 134 | error, 135 | ] = useCreateUserWithEmailAndPassword(auth); 136 | 137 | if (error) { 138 | return ( 139 |
140 |

Error: {error.message}

141 |
142 | ); 143 | } 144 | if (loading) { 145 | return

Loading...

; 146 | } 147 | if (user) { 148 | return ( 149 |
150 |

Registered User: {user.email}

151 |
152 | ); 153 | } 154 | return ( 155 |
156 | setEmail(e.target.value)} 160 | /> 161 | setPassword(e.target.value)} 165 | /> 166 | 169 |
170 | ); 171 | }; 172 | ``` 173 | 174 | ### useSignInWithEmailAndPassword 175 | 176 | ```js 177 | const [ 178 | signInWithEmailAndPassword, 179 | user, 180 | loading, 181 | error, 182 | ] = useSignInWithEmailAndPassword(auth); 183 | ``` 184 | 185 | Login a user with email and password. Wraps the underlying `auth.signInWithEmailAndPassword` method and provides additional `loading` and `error` information. 186 | 187 | The `useSignInWithEmailAndPassword` hook takes the following parameters: 188 | 189 | - `auth`: `Auth` instance for the app you would like to monitor 190 | 191 | Returns: 192 | 193 | - `signInWithEmailAndPassword(email: string, password: string)`: a function you can call to start the login 194 | - `user`: The `auth.User` if the user was logged in or `undefined` if not 195 | - `loading`: A `boolean` to indicate whether the the user login is processing 196 | - `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error 197 | 198 | #### Full Example 199 | 200 | ```jsx 201 | import { useSignInWithEmailAndPassword } from 'react-firebase-hooks/auth'; 202 | 203 | const SignIn = () => { 204 | const [email, setEmail] = useState(''); 205 | const [password, setPassword] = useState(''); 206 | const [ 207 | signInWithEmailAndPassword, 208 | user, 209 | loading, 210 | error, 211 | ] = useSignInWithEmailAndPassword(auth); 212 | 213 | if (error) { 214 | return ( 215 |
216 |

Error: {error.message}

217 |
218 | ); 219 | } 220 | if (loading) { 221 | return

Loading...

; 222 | } 223 | if (user) { 224 | return ( 225 |
226 |

Signed In User: {user.email}

227 |
228 | ); 229 | } 230 | return ( 231 |
232 | setEmail(e.target.value)} 236 | /> 237 | setPassword(e.target.value)} 241 | /> 242 | 245 |
246 | ); 247 | }; 248 | ``` 249 | 250 | ### useSignInWithApple 251 | 252 | ```js 253 | const [signInWithApple, user, loading, error] = useSignInWithApple(auth); 254 | ``` 255 | 256 | Login a user with Apple Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. 257 | 258 | The `useSignInWithApple` hook takes the following parameters: 259 | 260 | - `auth`: `Auth` instance for the app you would like to monitor 261 | 262 | Returns: 263 | 264 | - `signInWithApple(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login 265 | - `user`: The `auth.User` if the user was logged in or `undefined` if not 266 | - `loading`: A `boolean` to indicate whether the the user login is processing 267 | - `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error 268 | 269 | #### Full example 270 | 271 | See [social login example](#social-login-example) 272 | 273 | ### useSignInWithFacebook 274 | 275 | ```js 276 | const [signInWithFacebook, user, loading, error] = useSignInWithFacebook(auth); 277 | ``` 278 | 279 | Login a user with Facebook Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. 280 | 281 | The `useSignInWithApple` hook takes the following parameters: 282 | 283 | - `auth`: `Auth` instance for the app you would like to monitor 284 | 285 | Returns: 286 | 287 | - `signInWithFacebook(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login 288 | - `user`: The `auth.User` if the user was logged in or `undefined` if not 289 | - `loading`: A `boolean` to indicate whether the the user login is processing 290 | - `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error 291 | 292 | #### Full example 293 | 294 | See [social login example](#social-login-example) 295 | 296 | ### useSignInWithGithub 297 | 298 | ```js 299 | const [signInWithGithub, user, loading, error] = useSignInWithGithub(auth); 300 | ``` 301 | 302 | Login a user with Github Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. 303 | 304 | The `useSignInWithGithub` hook takes the following parameters: 305 | 306 | - `auth`: `Auth` instance for the app you would like to monitor 307 | 308 | Returns: 309 | 310 | - `signInWithGithub(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login 311 | - `user`: The `auth.User` if the user was logged in or `undefined` if not 312 | - `loading`: A `boolean` to indicate whether the the user login is processing 313 | - `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error 314 | 315 | #### Full example 316 | 317 | See [social login example](#social-login-example) 318 | 319 | ### useSignInWithGoogle 320 | 321 | ```js 322 | const [signInWithGoogle, user, loading, error] = useSignInWithGoogle(auth); 323 | ``` 324 | 325 | Login a user with Google Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.GoogleProvider` and provides additional `loading` and `error` information. 326 | 327 | The `useSignInWithGoogle` hook takes the following parameters: 328 | 329 | - `auth`: `Auth` instance for the app you would like to monitor 330 | 331 | Returns: 332 | 333 | - `signInWithGoogle(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login 334 | - `user`: The `auth.User` if the user was logged in or `undefined` if not 335 | - `loading`: A `boolean` to indicate whether the the user login is processing 336 | - `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error 337 | 338 | #### Full example 339 | 340 | See [social login example](#social-login-example) 341 | 342 | ### useSignInWithMicrosoft 343 | 344 | ```js 345 | const [signInWithMicrosoft, user, loading, error] = useSignInWithMicrosoft( 346 | auth 347 | ); 348 | ``` 349 | 350 | Login a user with Microsoftt Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. 351 | 352 | The `useSignInWithMicrosoft` hook takes the following parameters: 353 | 354 | - `auth`: `Auth` instance for the app you would like to monitor 355 | 356 | Returns: 357 | 358 | - `signInWithMicrosoft(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login 359 | - `user`: The `auth.User` if the user was logged in or `undefined` if not 360 | - `loading`: A `boolean` to indicate whether the the user login is processing 361 | - `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error 362 | 363 | #### Full example 364 | 365 | See [social login example](#social-login-example) 366 | 367 | ### useSignInWithTwittter 368 | 369 | ```js 370 | const [signInWithTwitter, user, loading, error] = useSignInWithTwitter(auth); 371 | ``` 372 | 373 | Login a user with Twitter Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. 374 | 375 | The `useSignInWithTwitter` hook takes the following parameters: 376 | 377 | - `auth`: `Auth` instance for the app you would like to monitor 378 | 379 | Returns: 380 | 381 | - `signInWithTwitter(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login 382 | - `user`: The `auth.User` if the user was logged in or `undefined` if not 383 | - `loading`: A `boolean` to indicate whether the the user login is processing 384 | - `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error 385 | 386 | #### Full example 387 | 388 | See [social login example](#social-login-example) 389 | 390 | ### useSignInWithYahoo 391 | 392 | ```js 393 | const [signInWithYahoo, user, loading, error] = useSignInWithYahoo(auth); 394 | ``` 395 | 396 | Login a user with Yahoo Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. 397 | 398 | The `useSignInWithYahoo` hook takes the following parameters: 399 | 400 | - `auth`: `Auth` instance for the app you would like to monitor 401 | 402 | Returns: 403 | 404 | - `signInWithYahoo(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login 405 | - `user`: The `auth.User` if the user was logged in or `undefined` if not 406 | - `loading`: A `boolean` to indicate whether the the user login is processing 407 | - `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error 408 | 409 | #### Full example 410 | 411 | See [social login example](#social-login-example) 412 | 413 | ### Social Login Example 414 | 415 | ```jsx 416 | import { useSignInWithXXX } from 'react-firebase-hooks/auth'; 417 | 418 | const SignIn = () => { 419 | const [signInWithXXX, user, loading, error] = useSignInWithXXX(auth); 420 | 421 | if (error) { 422 | return ( 423 |
424 |

Error: {error.message}

425 |
426 | ); 427 | } 428 | if (loading) { 429 | return

Loading...

; 430 | } 431 | if (user) { 432 | return ( 433 |
434 |

Signed In User: {user.email}

435 |
436 | ); 437 | } 438 | return ( 439 |
440 | setEmail(e.target.value)} 444 | /> 445 | setPassword(e.target.value)} 449 | /> 450 | 451 |
452 | ); 453 | }; 454 | ``` 455 | 456 | ### useUpdateEmail 457 | 458 | ```js 459 | const [updateEmail, updating, error] = useUpdateEmail(auth); 460 | ``` 461 | 462 | Update the current user's email address. Wraps the underlying `auth.updateEmail` method and provides additional `updating` and `error` information. 463 | 464 | The `useUpdateEmail` hook takes the following parameters: 465 | 466 | - `auth`: `Auth` instance for the app you would like to monitor 467 | 468 | Returns: 469 | 470 | - `updateEmail(email: string)`: a function you can call to update the current user's email addres 471 | - `updating`: A `boolean` to indicate whether the user update is processing 472 | - `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error 473 | 474 | #### Full Example 475 | 476 | ```jsx 477 | import { useUpdateEmail } from 'react-firebase-hooks/auth'; 478 | 479 | const UpdateEmail = () => { 480 | const [email, setEmail] = useState(''); 481 | const [updateEmail, updating, error] = useUpdateEmail(auth); 482 | 483 | if (error) { 484 | return ( 485 |
486 |

Error: {error.message}

487 |
488 | ); 489 | } 490 | if (updating) { 491 | return

Updating...

; 492 | } 493 | return ( 494 |
495 | setEmail(e.target.value)} 499 | /> 500 | 508 |
509 | ); 510 | }; 511 | ``` 512 | 513 | ### useUpdatePassword 514 | 515 | ```js 516 | const [updatePassword, updating, error] = useUpdatePassword(auth); 517 | ``` 518 | 519 | Update the current user's password. Wraps the underlying `auth.updatePassword` method and provides additional `updating` and `error` information. 520 | 521 | The `useUpdatePassword` hook takes the following parameters: 522 | 523 | - `auth`: `Auth` instance for the app you would like to monitor 524 | 525 | Returns: 526 | 527 | - `updatePassword(password: string)`: a function you can call to update the current user's password 528 | - `updating`: A `boolean` to indicate whether the user update is processing 529 | - `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error 530 | 531 | #### Full Example 532 | 533 | ```jsx 534 | import { useUpdatePassword } from 'react-firebase-hooks/auth'; 535 | 536 | const UpdatePassword = () => { 537 | const [password, setPassword] = useState(''); 538 | const [updatePassword, updating, error] = useUpdatePassword(auth); 539 | 540 | if (error) { 541 | return ( 542 |
543 |

Error: {error.message}

544 |
545 | ); 546 | } 547 | if (updating) { 548 | return

Updating...

; 549 | } 550 | return ( 551 |
552 | setPassword(e.target.value)} 556 | /> 557 | 565 |
566 | ); 567 | }; 568 | ``` 569 | 570 | ### useUpdateProfile 571 | 572 | ```js 573 | const [updateProfile, updating, error] = useUpdateProfile(auth); 574 | ``` 575 | 576 | Update the current user's profile. Wraps the underlying `auth.updateProfile` method and provides additional `updating` and `error` information. 577 | 578 | The `useUpdateProfile` hook takes the following parameters: 579 | 580 | - `auth`: `Auth` instance for the app you would like to monitor 581 | 582 | Returns: 583 | 584 | - `updateProfile({ displayName: string, photoURL: string })`: a function you can call to update the current user's profile 585 | - `updating`: A `boolean` to indicate whether the user update is processing 586 | - `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error 587 | 588 | #### Full Example 589 | 590 | ```jsx 591 | import { useUpdateProfile } from 'react-firebase-hooks/auth'; 592 | 593 | const UpdateProfile = () => { 594 | const [displayName, setDisplayName] = useState(''); 595 | const [photoURL, setPhotoURL] = useState(''); 596 | const [updateProfile, updating, error] = useUpdateProfile(auth); 597 | 598 | if (error) { 599 | return ( 600 |
601 |

Error: {error.message}

602 |
603 | ); 604 | } 605 | if (updating) { 606 | return

Updating...

; 607 | } 608 | return ( 609 |
610 | setDisplayName(e.target.value)} 614 | /> 615 | setPhotoURL(e.target.value)} 619 | /> 620 | 628 |
629 | ); 630 | }; 631 | ``` 632 | 633 | ### useSendPasswordResetEmail 634 | 635 | ```js 636 | const [sendPasswordResetEmail, sending, error] = useSendPasswordResetEmail( 637 | auth 638 | ); 639 | ``` 640 | 641 | Send a password reset email to the specified email address. Wraps the underlying `auth.sendPasswordResetEmail` method and provides additional `sending` and `error` information. 642 | 643 | The `useSendPasswordResetEmail` hook takes the following parameters: 644 | 645 | - `auth`: `Auth` instance for the app you would like to monitor 646 | 647 | Returns: 648 | 649 | - `sendPasswordResetEmail(email: string)`: a function you can call to send a password reset emaail 650 | - `sending`: A `boolean` to indicate whether the email is being sent 651 | - `error`: Any `Error` returned by Firebase when trying to send the email, or `undefined` if there is no error 652 | 653 | #### Full Example 654 | 655 | ```jsx 656 | import { useSendPasswordResetEmail } from 'react-firebase-hooks/auth'; 657 | 658 | const SendPasswordReset = () => { 659 | const [email, setEmail] = useState(''); 660 | const [sendPasswordResetEmail, sending, error] = useSendPasswordResetEmail( 661 | auth 662 | ); 663 | 664 | if (error) { 665 | return ( 666 |
667 |

Error: {error.message}

668 |
669 | ); 670 | } 671 | if (sending) { 672 | return

Sending...

; 673 | } 674 | return ( 675 |
676 | setEmail(e.target.value)} 680 | /> 681 | 689 |
690 | ); 691 | }; 692 | ``` 693 | 694 | ### useSendEmailVerification 695 | 696 | ```js 697 | const [sendEmailVerification, sending, error] = useSendEmailVerification(auth); 698 | ``` 699 | 700 | Send a verification email to the current user. Wraps the underlying `auth.sendEmailVerification` method and provides additional `sending` and `error` information. 701 | 702 | The `useSendEmailVerification` hook takes the following parameters: 703 | 704 | - `auth`: `Auth` instance for the app you would like to monitor 705 | 706 | Returns: 707 | 708 | - `sendEmailVerification()`: a function you can call to send a password reset emaail 709 | - `sending`: A `boolean` to indicate whether the email is being sent 710 | - `error`: Any `Error` returned by Firebase when trying to send the email, or `undefined` if there is no error 711 | 712 | #### Full Example 713 | 714 | ```jsx 715 | import { useSendEmailVerification } from 'react-firebase-hooks/auth'; 716 | 717 | const SendEmailVerification = () => { 718 | const [email, setEmail] = useState(''); 719 | const [sendEmailVerification, sending, error] = useSendEmailVerification( 720 | auth 721 | ); 722 | 723 | if (error) { 724 | return ( 725 |
726 |

Error: {error.message}

727 |
728 | ); 729 | } 730 | if (sending) { 731 | return

Sending...

; 732 | } 733 | return ( 734 |
735 | 743 |
744 | ); 745 | }; 746 | ``` 747 | -------------------------------------------------------------------------------- /auth/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useAuthState, AuthStateHook } from './useAuthState'; 2 | export { default as useCreateUserWithEmailAndPassword } from './useCreateUserWithEmailAndPassword'; 3 | export { 4 | default as useSendEmailVerification, 5 | SendEmailVerificationHook, 6 | } from './useSendEmailVerification'; 7 | export { 8 | default as useSendPasswordResetEmail, 9 | SendPasswordResetEmailHook, 10 | } from './useSendPasswordResetEmail'; 11 | export { default as useSignInWithEmailAndPassword } from './useSignInWithEmailAndPassword'; 12 | export { 13 | useSignInWithApple, 14 | useSignInWithFacebook, 15 | useSignInWithGithub, 16 | useSignInWithGoogle, 17 | useSignInWithMicrosoft, 18 | useSignInWithTwitter, 19 | useSignInWithYahoo, 20 | } from './useSignInWithPopup'; 21 | export { 22 | useUpdateEmail, 23 | useUpdatePassword, 24 | useUpdateProfile, 25 | UpdateEmailHook, 26 | UpdatePasswordHook, 27 | UpdateProfileHook, 28 | } from './useUpdateUser'; 29 | 30 | export { EmailAndPasswordActionHook, SignInWithPopupHook } from './types'; 31 | -------------------------------------------------------------------------------- /auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-hooks/auth", 3 | "main": "dist/index.cjs.js", 4 | "module": "dist/index.esm.js", 5 | "typings": "dist/auth/index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /auth/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionCodeSettings, 3 | AuthError, 4 | CustomParameters, 5 | UserCredential, 6 | } from 'firebase/auth'; 7 | 8 | export type AuthActionHook = [ 9 | M, 10 | UserCredential | undefined, 11 | boolean, 12 | AuthError | undefined 13 | ]; 14 | export type CreateUserOptions = { 15 | emailVerificationOptions?: ActionCodeSettings; 16 | sendEmailVerification?: boolean; 17 | }; 18 | export type EmailAndPasswordActionHook = AuthActionHook< 19 | (email: string, password: string) => Promise 20 | >; 21 | 22 | export type SignInWithPopupHook = AuthActionHook< 23 | (scopes?: string[], customOAuthParameters?: CustomParameters) => Promise 24 | >; 25 | -------------------------------------------------------------------------------- /auth/useAuthState.ts: -------------------------------------------------------------------------------- 1 | import { Auth, onAuthStateChanged, User } from 'firebase/auth'; 2 | import { useEffect, useMemo } from 'react'; 3 | import { LoadingHook, useLoadingValue } from '../util'; 4 | 5 | export type AuthStateHook = LoadingHook; 6 | 7 | type AuthStateOptions = { 8 | onUserChanged?: (user: User | null) => Promise; 9 | }; 10 | 11 | export default (auth: Auth, options?: AuthStateOptions): AuthStateHook => { 12 | const { error, loading, setError, setValue, value } = useLoadingValue< 13 | User | null, 14 | Error 15 | >(() => auth.currentUser); 16 | 17 | useEffect(() => { 18 | const listener = onAuthStateChanged( 19 | auth, 20 | async (user) => { 21 | if (options?.onUserChanged) { 22 | // onUserChanged function to process custom claims on any other trigger function 23 | try { 24 | await options.onUserChanged(user); 25 | } catch (e) { 26 | setError(e as Error); 27 | } 28 | } 29 | setValue(user); 30 | }, 31 | setError 32 | ); 33 | 34 | return () => { 35 | listener(); 36 | }; 37 | }, [auth]); 38 | 39 | const resArray: AuthStateHook = [value, loading, error]; 40 | return useMemo(() => resArray, resArray); 41 | }; 42 | -------------------------------------------------------------------------------- /auth/useCreateUserWithEmailAndPassword.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Auth, 3 | AuthError, 4 | createUserWithEmailAndPassword as firebaseCreateUserWithEmailAndPassword, 5 | sendEmailVerification, 6 | UserCredential, 7 | } from 'firebase/auth'; 8 | import { useMemo, useState } from 'react'; 9 | import { CreateUserOptions, EmailAndPasswordActionHook } from './types'; 10 | 11 | export default ( 12 | auth: Auth, 13 | options?: CreateUserOptions 14 | ): EmailAndPasswordActionHook => { 15 | const [error, setError] = useState(); 16 | const [registeredUser, setRegisteredUser] = useState(); 17 | const [loading, setLoading] = useState(false); 18 | 19 | const createUserWithEmailAndPassword = async ( 20 | email: string, 21 | password: string 22 | ) => { 23 | setLoading(true); 24 | setError(undefined); 25 | try { 26 | const user = await firebaseCreateUserWithEmailAndPassword( 27 | auth, 28 | email, 29 | password 30 | ); 31 | if (options && options.sendEmailVerification && user.user) { 32 | await sendEmailVerification( 33 | user.user, 34 | options.emailVerificationOptions 35 | ); 36 | } 37 | setRegisteredUser(user); 38 | } catch (error) { 39 | setError(error as AuthError); 40 | } finally { 41 | setLoading(false); 42 | } 43 | }; 44 | 45 | const resArray: EmailAndPasswordActionHook = [ 46 | createUserWithEmailAndPassword, 47 | registeredUser, 48 | loading, 49 | error, 50 | ]; 51 | return useMemo(() => resArray, resArray); 52 | }; 53 | -------------------------------------------------------------------------------- /auth/useSendEmailVerification.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Auth, 3 | AuthError, 4 | sendEmailVerification as fbSendEmailVerification, 5 | } from 'firebase/auth'; 6 | import { useMemo, useState } from 'react'; 7 | 8 | export type SendEmailVerificationHook = [ 9 | () => Promise, 10 | boolean, 11 | AuthError | Error | undefined 12 | ]; 13 | 14 | export default (auth: Auth): SendEmailVerificationHook => { 15 | const [error, setError] = useState(); 16 | const [loading, setLoading] = useState(false); 17 | 18 | const sendEmailVerification = async () => { 19 | setLoading(true); 20 | setError(undefined); 21 | try { 22 | if (auth.currentUser) { 23 | await fbSendEmailVerification(auth.currentUser); 24 | } else { 25 | setError(new Error('No user is logged in') as AuthError); 26 | } 27 | } catch (err) { 28 | setError(err as AuthError); 29 | } finally { 30 | setLoading(false); 31 | } 32 | }; 33 | 34 | const resArray: SendEmailVerificationHook = [ 35 | sendEmailVerification, 36 | loading, 37 | error, 38 | ]; 39 | return useMemo(() => resArray, resArray); 40 | }; 41 | -------------------------------------------------------------------------------- /auth/useSendPasswordResetEmail.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Auth, 3 | AuthError, 4 | sendPasswordResetEmail as fbSendPasswordResetEmail, 5 | } from 'firebase/auth'; 6 | import { useMemo, useState } from 'react'; 7 | 8 | export type SendPasswordResetEmailHook = [ 9 | (email: string) => Promise, 10 | boolean, 11 | AuthError | Error | undefined 12 | ]; 13 | 14 | export default (auth: Auth): SendPasswordResetEmailHook => { 15 | const [error, setError] = useState(); 16 | const [loading, setLoading] = useState(false); 17 | 18 | const sendPasswordResetEmail = async (email: string) => { 19 | setLoading(true); 20 | setError(undefined); 21 | try { 22 | await fbSendPasswordResetEmail(auth, email); 23 | } catch (err) { 24 | setError(err as AuthError); 25 | } finally { 26 | setLoading(false); 27 | } 28 | }; 29 | 30 | const resArray: SendPasswordResetEmailHook = [ 31 | sendPasswordResetEmail, 32 | loading, 33 | error, 34 | ]; 35 | return useMemo(() => resArray, resArray); 36 | }; 37 | -------------------------------------------------------------------------------- /auth/useSignInWithEmailAndPassword.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Auth, 3 | UserCredential, 4 | signInWithEmailAndPassword as firebaseSignInWithEmailAndPassword, 5 | AuthError, 6 | } from 'firebase/auth'; 7 | import { useState, useMemo } from 'react'; 8 | import { EmailAndPasswordActionHook } from './types'; 9 | 10 | export default (auth: Auth): EmailAndPasswordActionHook => { 11 | const [error, setError] = useState(); 12 | const [loggedInUser, setLoggedInUser] = useState(); 13 | const [loading, setLoading] = useState(false); 14 | 15 | const signInWithEmailAndPassword = async ( 16 | email: string, 17 | password: string 18 | ) => { 19 | setLoading(true); 20 | setError(undefined); 21 | try { 22 | const user = await firebaseSignInWithEmailAndPassword( 23 | auth, 24 | email, 25 | password 26 | ); 27 | setLoggedInUser(user); 28 | } catch (err) { 29 | setError(err as AuthError); 30 | } finally { 31 | setLoading(false); 32 | } 33 | }; 34 | 35 | const resArray: EmailAndPasswordActionHook = [ 36 | signInWithEmailAndPassword, 37 | loggedInUser, 38 | loading, 39 | error, 40 | ]; 41 | return useMemo(() => resArray, resArray); 42 | }; 43 | -------------------------------------------------------------------------------- /auth/useSignInWithPopup.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Auth, 3 | AuthError, 4 | AuthProvider, 5 | CustomParameters, 6 | FacebookAuthProvider, 7 | GithubAuthProvider, 8 | GoogleAuthProvider, 9 | OAuthProvider, 10 | signInWithPopup, 11 | TwitterAuthProvider, 12 | UserCredential, 13 | } from 'firebase/auth'; 14 | import { useMemo, useState } from 'react'; 15 | import { SignInWithPopupHook } from './types'; 16 | 17 | export const useSignInWithApple = (auth: Auth): SignInWithPopupHook => { 18 | return useSignInWithOAuth(auth, 'apple.com'); 19 | }; 20 | 21 | export const useSignInWithFacebook = (auth: Auth): SignInWithPopupHook => { 22 | const createFacebookAuthProvider = ( 23 | scopes?: string[], 24 | customOAuthParameters?: CustomParameters 25 | ) => { 26 | const provider = new FacebookAuthProvider(); 27 | if (scopes) { 28 | scopes.forEach((scope) => provider.addScope(scope)); 29 | } 30 | if (customOAuthParameters) { 31 | provider.setCustomParameters(customOAuthParameters); 32 | } 33 | return provider; 34 | }; 35 | return useSignInWithPopup(auth, createFacebookAuthProvider); 36 | }; 37 | 38 | export const useSignInWithGithub = (auth: Auth): SignInWithPopupHook => { 39 | const createGithubAuthProvider = ( 40 | scopes?: string[], 41 | customOAuthParameters?: CustomParameters 42 | ) => { 43 | const provider = new GithubAuthProvider(); 44 | if (scopes) { 45 | scopes.forEach((scope) => provider.addScope(scope)); 46 | } 47 | if (customOAuthParameters) { 48 | provider.setCustomParameters(customOAuthParameters); 49 | } 50 | return provider; 51 | }; 52 | return useSignInWithPopup(auth, createGithubAuthProvider); 53 | }; 54 | 55 | export const useSignInWithGoogle = (auth: Auth): SignInWithPopupHook => { 56 | const createGoogleAuthProvider = ( 57 | scopes?: string[], 58 | customOAuthParameters?: CustomParameters 59 | ) => { 60 | const provider = new GoogleAuthProvider(); 61 | if (scopes) { 62 | scopes.forEach((scope) => provider.addScope(scope)); 63 | } 64 | if (customOAuthParameters) { 65 | provider.setCustomParameters(customOAuthParameters); 66 | } 67 | return provider; 68 | }; 69 | return useSignInWithPopup(auth, createGoogleAuthProvider); 70 | }; 71 | 72 | export const useSignInWithMicrosoft = (auth: Auth): SignInWithPopupHook => { 73 | return useSignInWithOAuth(auth, 'microsoft.com'); 74 | }; 75 | 76 | export const useSignInWithTwitter = (auth: Auth): SignInWithPopupHook => { 77 | const createTwitterAuthProvider = ( 78 | scopes?: string[], 79 | customOAuthParameters?: CustomParameters 80 | ) => { 81 | const provider = new TwitterAuthProvider(); 82 | if (scopes) { 83 | scopes.forEach((scope) => provider.addScope(scope)); 84 | } 85 | if (customOAuthParameters) { 86 | provider.setCustomParameters(customOAuthParameters); 87 | } 88 | return provider; 89 | }; 90 | return useSignInWithPopup(auth, createTwitterAuthProvider); 91 | }; 92 | 93 | export const useSignInWithYahoo = (auth: Auth): SignInWithPopupHook => { 94 | return useSignInWithOAuth(auth, 'yahoo.com'); 95 | }; 96 | 97 | const useSignInWithOAuth = ( 98 | auth: Auth, 99 | providerId: string 100 | ): SignInWithPopupHook => { 101 | const createOAuthProvider = ( 102 | scopes?: string[], 103 | customOAuthParameters?: CustomParameters 104 | ) => { 105 | const provider = new OAuthProvider(providerId); 106 | if (scopes) { 107 | scopes.forEach((scope) => provider.addScope(scope)); 108 | } 109 | if (customOAuthParameters) { 110 | provider.setCustomParameters(customOAuthParameters); 111 | } 112 | return provider; 113 | }; 114 | return useSignInWithPopup(auth, createOAuthProvider); 115 | }; 116 | 117 | const useSignInWithPopup = ( 118 | auth: Auth, 119 | createProvider: ( 120 | scopes?: string[], 121 | customOAuthParameters?: CustomParameters 122 | ) => AuthProvider 123 | ): SignInWithPopupHook => { 124 | const [error, setError] = useState(); 125 | const [loggedInUser, setLoggedInUser] = useState(); 126 | const [loading, setLoading] = useState(false); 127 | 128 | const signInWithGoogle = async ( 129 | scopes?: string[], 130 | customOAuthParameters?: CustomParameters 131 | ) => { 132 | setLoading(true); 133 | setError(undefined); 134 | try { 135 | const provider = createProvider(scopes, customOAuthParameters); 136 | const user = await signInWithPopup(auth, provider); 137 | setLoggedInUser(user); 138 | } catch (err) { 139 | setError(err as AuthError); 140 | } finally { 141 | setLoading(false); 142 | } 143 | }; 144 | 145 | const resArray: SignInWithPopupHook = [ 146 | signInWithGoogle, 147 | loggedInUser, 148 | loading, 149 | error, 150 | ]; 151 | return useMemo(() => resArray, resArray); 152 | }; 153 | -------------------------------------------------------------------------------- /auth/useUpdateUser.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Auth, 3 | AuthError, 4 | updateEmail as fbUpdateEmail, 5 | updatePassword as fbUpdatePassword, 6 | updateProfile as fbUpdateProfile, 7 | } from 'firebase/auth'; 8 | import { useMemo, useState } from 'react'; 9 | 10 | type Profile = { 11 | displayName?: string | null; 12 | photoURL?: string | null; 13 | }; 14 | 15 | export type UpdateUserHook = [M, boolean, AuthError | Error | undefined]; 16 | 17 | export type UpdateEmailHook = UpdateUserHook<(email: string) => Promise>; 18 | export type UpdatePasswordHook = UpdateUserHook< 19 | (password: string) => Promise 20 | >; 21 | export type UpdateProfileHook = UpdateUserHook< 22 | (profile: Profile) => Promise 23 | >; 24 | 25 | export const useUpdateEmail = (auth: Auth): UpdateEmailHook => { 26 | const [error, setError] = useState(); 27 | const [loading, setLoading] = useState(false); 28 | 29 | const updateEmail = async (email: string) => { 30 | setLoading(true); 31 | setError(undefined); 32 | try { 33 | if (auth.currentUser) { 34 | await fbUpdateEmail(auth.currentUser, email); 35 | } else { 36 | setError(new Error('No user is logged in') as AuthError); 37 | } 38 | } catch (err) { 39 | setError(err as AuthError); 40 | } finally { 41 | setLoading(false); 42 | } 43 | }; 44 | 45 | const resArray: UpdateEmailHook = [updateEmail, loading, error]; 46 | return useMemo(() => resArray, resArray); 47 | }; 48 | 49 | export const useUpdatePassword = (auth: Auth): UpdatePasswordHook => { 50 | const [error, setError] = useState(); 51 | const [loading, setLoading] = useState(false); 52 | 53 | const updatePassword = async (password: string) => { 54 | setLoading(true); 55 | setError(undefined); 56 | try { 57 | if (auth.currentUser) { 58 | await fbUpdatePassword(auth.currentUser, password); 59 | } else { 60 | setError(new Error('No user is logged in') as AuthError); 61 | } 62 | } catch (err) { 63 | setError(err as AuthError); 64 | } finally { 65 | setLoading(false); 66 | } 67 | }; 68 | 69 | const resArray: UpdatePasswordHook = [updatePassword, loading, error]; 70 | return useMemo(() => resArray, resArray); 71 | }; 72 | 73 | export const useUpdateProfile = (auth: Auth): UpdateProfileHook => { 74 | const [error, setError] = useState(); 75 | const [loading, setLoading] = useState(false); 76 | 77 | const updateProfile = async (profile: Profile) => { 78 | setLoading(true); 79 | setError(undefined); 80 | try { 81 | if (auth.currentUser) { 82 | await fbUpdateProfile(auth.currentUser, profile); 83 | } else { 84 | setError(new Error('No user is logged in') as AuthError); 85 | } 86 | } catch (err) { 87 | setError(err as AuthError); 88 | } finally { 89 | setLoading(false); 90 | } 91 | }; 92 | 93 | const resArray: UpdateProfileHook = [updateProfile, loading, error]; 94 | return useMemo(() => resArray, resArray); 95 | }; 96 | -------------------------------------------------------------------------------- /database/README.md: -------------------------------------------------------------------------------- 1 | # React Firebase Hooks - Realtime Database 2 | 3 | React Firebase Hooks provides convenience listeners for lists and values stored within the 4 | Firebase Realtime Database. The hooks wrap around the `onX(...)` method. 5 | 6 | In addition to returning the list or value, the hooks provide an `error` and `loading` property 7 | to give a complete lifecycle for loading and listening to the Realtime Database. 8 | 9 | All hooks can be imported from `react-firebase-hooks/database`, e.g. 10 | 11 | ```js 12 | import { useList } from 'react-firebase-hooks/database'; 13 | ``` 14 | 15 | List of Realtime Database hooks: 16 | 17 | - [React Firebase Hooks - Realtime Database](#react-firebase-hooks---realtime-database) 18 | - [useList](#uselist) 19 | - [Full Example](#full-example) 20 | - [useListKeys](#uselistkeys) 21 | - [useListVals](#uselistvals) 22 | - [useObject](#useobject) 23 | - [Full Example](#full-example-1) 24 | - [useObjectVal](#useobjectval) 25 | - [Transforming data](#transforming-data) 26 | - [Full Example](#full-example-2) 27 | 28 | Additional functionality: 29 | 30 | - [Transforming data](#transforming-data) 31 | 32 | ### useList 33 | 34 | ```js 35 | const [snapshots, loading, error] = useList(reference); 36 | ``` 37 | 38 | Retrieve and monitor a list value in the Firebase Realtime Database. 39 | 40 | The `useList` hook takes the following parameters: 41 | 42 | - `reference`: (optional) `database.Reference` for the data you would like to load 43 | 44 | Returns: 45 | 46 | - `snapshots`: an array of `database.DataSnapshot`, or `undefined` if no reference is supplied 47 | - `loading`: a `boolean` to indicate if the data is still being loaded 48 | - `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error 49 | 50 | #### Full Example 51 | 52 | ```js 53 | import { ref, getDatabase } from 'firebase/database'; 54 | import { useList } from 'react-firebase-hooks/database'; 55 | 56 | const database = getDatabase(firebaseApp); 57 | 58 | const DatabaseList = () => { 59 | const [snapshots, loading, error] = useList(ref(database, 'list')); 60 | 61 | return ( 62 |
63 |

64 | {error && Error: {error}} 65 | {loading && List: Loading...} 66 | {!loading && snapshots && ( 67 | 68 | 69 | List:{' '} 70 | {snapshots.map((v) => ( 71 | {v.val()}, 72 | ))} 73 | 74 | 75 | )} 76 |

77 |
78 | ); 79 | }; 80 | ``` 81 | 82 | ### useListKeys 83 | 84 | ```js 85 | const [keys, loading, error] = useListKeys(reference); 86 | ``` 87 | 88 | As `useList`, but this hooks extracts the `database.DataSnapshot.key` values, rather than the the `database.DataSnapshot`s themselves. 89 | 90 | The `useListKeys` hook takes the following parameters: 91 | 92 | - `reference`: (optional) `database.Reference` for the data you would like to load 93 | 94 | Returns: 95 | 96 | - `keys`: an array of `string`, or `undefined` if no reference is supplied 97 | - `loading`: a `boolean` to indicate if the data is still being loaded 98 | - `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error 99 | 100 | ### useListVals 101 | 102 | ```js 103 | const [values, loading, error] = useListVals (reference, options); 104 | ``` 105 | 106 | As `useList`, but this hook extracts a typed list of the `database.DataSnapshot.val()` values, rather than the the 107 | `database.DataSnapshot`s themselves. 108 | 109 | The `useListVals` hook takes the following parameters: 110 | 111 | - `reference`: (optional) `database.Reference` for the data you would like to load 112 | - `options`: (optional) `Object` with the following parameters: 113 | - `keyField`: (optional) `string` field name that should be populated with the `database.DataSnapshot.id` property in the returned values. 114 | - `refField`: (optional) `string` field name that should be populated with the `database.DataSnapshot.ref` property. 115 | - `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. 116 | 117 | Returns: 118 | 119 | - `values`: an array of `T`, or `undefined` if no reference is supplied 120 | - `loading`: a `boolean` to indicate if the data is still being loaded 121 | - `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error 122 | 123 | ### useObject 124 | 125 | ```js 126 | const [snapshot, loading, error] = useObject(reference); 127 | ``` 128 | 129 | Retrieve and monitor an object or primitive value in the Firebase Realtime Database. 130 | 131 | The `useObject` hook takes the following parameters: 132 | 133 | - `reference`: (optional) `database.Reference` for the data you would like to load 134 | 135 | Returns: 136 | 137 | - `snapshot`: a `database.DataSnapshot`, or `undefined` if no reference is supplied 138 | - `loading`: a `boolean` to indicate if the data is still being loaded 139 | - `error`: Any `Error` returned by Firebase when trying to load the data, or `undefined` if there is no error 140 | 141 | #### Full Example 142 | 143 | ```js 144 | import { ref, getDatabase } from 'firebase/database'; 145 | import { useObject } from 'react-firebase-hooks/database'; 146 | 147 | const database = getDatabase(firebaseApp); 148 | 149 | const DatabaseValue = () => { 150 | const [snapshot, loading, error] = useObject(ref(database, 'value')); 151 | 152 | return ( 153 |
154 |

155 | {error && Error: {error}} 156 | {loading && Value: Loading...} 157 | {snapshot && Value: {snapshot.val()}} 158 |

159 |
160 | ); 161 | }; 162 | ``` 163 | 164 | ### useObjectVal 165 | 166 | ```js 167 | const [value, loading, error] = useObjectVal (reference, options); 168 | ``` 169 | 170 | As `useObject`, but this hook returns the typed contents of `database.DataSnapshot.val()`, rather than the the 171 | `database.DataSnapshot` itself. 172 | 173 | The `useObjectVal` hook takes the following parameters: 174 | 175 | - `reference`: (optional) `database.Reference` for the data you would like to load 176 | - `options`: (optional) `Object` with the following parameters: 177 | - `keyField`: (optional) `string` field name that should be populated with the `database.DataSnapshot.key` property in the returned value. 178 | - `refField`: (optional) `string` field name that should be populated with the `database.DataSnapshot.ref` property. 179 | - `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. 180 | 181 | Returns: 182 | 183 | - `value`: a `T`, or `undefined` if no reference is supplied 184 | - `loading`: a `boolean` to indicate if the data is still being loaded 185 | - `error`: Any `FirebaseError` returned by Firebase when trying to load the data, or `undefined` if there is no error 186 | 187 | ## Transforming data 188 | 189 | Firebase allows a restricted number of data types in the Realtime Database, which may not be flexible enough for your application. Both `useListVals` and `useObjectVal` support an optional `transform` function which allows the transformation of the underlying Firebase data into whatever format the application require, e.g. a `Date` type. 190 | 191 | ```js 192 | transform?: (val: any) => T; 193 | ``` 194 | 195 | The `transform` function is passed a single row of a data, so will be called once when used with `useObjectVal` and multiple times, when used with `useListVals`. 196 | 197 | The `transform` function will not receive the `key` or `ref` values referenced in the properties named in the `keyField` or `refField` options, nor it is expected to produce them. Either or both, if specified, will be merged afterwards. 198 | 199 | If the `transform` function is defined within your React component, it is recomended that you memoize the function to prevent unnecessry renders. 200 | 201 | #### Full Example 202 | 203 | ```js 204 | type SaleType = { 205 | idSale: string, 206 | date: Date, // <== it is declared as type Date which Firebase does not support. 207 | // ...Other fields 208 | }; 209 | const options = { 210 | keyField: 'idSale', 211 | transform: (val) => ({ 212 | ...val, 213 | date: new Date(val.date), 214 | }), 215 | }; 216 | export const useSale: ( 217 | idSale: string 218 | ) => [SaleType | undefined, boolean, any] = (idSale) => 219 | useObjectVal < SaleType > (database.ref(`sales/${idSale}`), options); 220 | export const useSales: () => [SaleType[] | undefined, boolean, any] = () => 221 | useListVals < SaleType > (database.ref('sales'), options); 222 | ``` 223 | 224 | The `transform` function might be used for various purposes: 225 | 226 | ```js 227 | transform: ({ firstName, lastName, someBool, ...val }) => ({ 228 | // Merge in default values, declared elsewhere: 229 | ...defaultValues, 230 | // Override them with the actual values 231 | ...val, 232 | // Create new fields from existing values 233 | fullName: `${firstName} ${lastName}`, 234 | // Ensure a field is a proper boolean instead of truish or falsy: 235 | someBool: !!someBool, 236 | // Same goes for any other poorly represented data type 237 | }); 238 | ``` 239 | -------------------------------------------------------------------------------- /database/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { DataSnapshot } from 'firebase/database'; 2 | 3 | export type ValOptions = { 4 | keyField?: string; 5 | refField?: string; 6 | transform?: (val: any) => T; 7 | }; 8 | 9 | const isObject = (val: any) => 10 | val != null && typeof val === 'object' && Array.isArray(val) === false; 11 | 12 | export const snapshotToData = ( 13 | snapshot: DataSnapshot, 14 | keyField?: string, 15 | refField?: string, 16 | transform?: (val: any) => T 17 | ) => { 18 | if (!snapshot.exists) { 19 | return undefined; 20 | } 21 | 22 | const val = snapshot.val(); 23 | if (isObject(val)) { 24 | return { 25 | ...(transform ? transform(val) : val), 26 | ...(keyField ? { [keyField]: snapshot.key } : null), 27 | ...(refField ? { [refField]: snapshot.ref } : null), 28 | }; 29 | } 30 | return transform ? transform(val) : val; 31 | }; 32 | -------------------------------------------------------------------------------- /database/helpers/useListReducer.ts: -------------------------------------------------------------------------------- 1 | import { DataSnapshot } from 'firebase/database'; 2 | import { useReducer } from 'react'; 3 | 4 | type KeyValueState = { 5 | keys?: string[]; 6 | values?: DataSnapshot[]; 7 | }; 8 | 9 | type ReducerState = { 10 | error?: Error; 11 | loading: boolean; 12 | value: KeyValueState; 13 | }; 14 | 15 | type AddAction = { 16 | type: 'add'; 17 | previousKey?: string | null; 18 | snapshot: DataSnapshot | null; 19 | }; 20 | type ChangeAction = { 21 | type: 'change'; 22 | snapshot: DataSnapshot | null; 23 | }; 24 | type EmptyAction = { type: 'empty' }; 25 | type ErrorAction = { type: 'error'; error: Error }; 26 | type MoveAction = { 27 | type: 'move'; 28 | previousKey?: string | null; 29 | snapshot: DataSnapshot | null; 30 | }; 31 | type RemoveAction = { 32 | type: 'remove'; 33 | snapshot: DataSnapshot | null; 34 | }; 35 | type ResetAction = { type: 'reset' }; 36 | type ValueAction = { type: 'value'; snapshots: DataSnapshot[] | null }; 37 | type ReducerAction = 38 | | AddAction 39 | | ChangeAction 40 | | EmptyAction 41 | | ErrorAction 42 | | MoveAction 43 | | RemoveAction 44 | | ResetAction 45 | | ValueAction; 46 | 47 | const initialState: ReducerState = { 48 | loading: true, 49 | value: { 50 | keys: [], 51 | values: [], 52 | }, 53 | }; 54 | 55 | const listReducer = ( 56 | state: ReducerState, 57 | action: ReducerAction 58 | ): ReducerState => { 59 | switch (action.type) { 60 | case 'add': 61 | if (!action.snapshot) { 62 | return state; 63 | } 64 | return { 65 | ...state, 66 | error: undefined, 67 | value: addChild(state.value, action.snapshot, action.previousKey), 68 | }; 69 | case 'change': 70 | if (!action.snapshot) { 71 | return state; 72 | } 73 | return { 74 | ...state, 75 | error: undefined, 76 | value: changeChild(state.value, action.snapshot), 77 | }; 78 | case 'error': 79 | return { 80 | ...state, 81 | error: action.error, 82 | loading: false, 83 | value: { 84 | keys: undefined, 85 | values: undefined, 86 | }, 87 | }; 88 | case 'move': 89 | if (!action.snapshot) { 90 | return state; 91 | } 92 | return { 93 | ...state, 94 | error: undefined, 95 | value: moveChild(state.value, action.snapshot, action.previousKey), 96 | }; 97 | case 'remove': 98 | if (!action.snapshot) { 99 | return state; 100 | } 101 | return { 102 | ...state, 103 | error: undefined, 104 | value: removeChild(state.value, action.snapshot), 105 | }; 106 | case 'reset': 107 | return initialState; 108 | case 'value': 109 | return { 110 | ...state, 111 | error: undefined, 112 | loading: false, 113 | value: setValue(action.snapshots), 114 | }; 115 | case 'empty': 116 | return { 117 | ...state, 118 | loading: false, 119 | value: { 120 | keys: undefined, 121 | values: undefined, 122 | }, 123 | }; 124 | default: 125 | return state; 126 | } 127 | }; 128 | 129 | const setValue = (snapshots: DataSnapshot[] | null): KeyValueState => { 130 | if (!snapshots) { 131 | return { 132 | keys: [], 133 | values: [], 134 | }; 135 | } 136 | 137 | const keys: string[] = []; 138 | const values: DataSnapshot[] = []; 139 | snapshots.forEach((snapshot) => { 140 | if (!snapshot.key) { 141 | return; 142 | } 143 | keys.push(snapshot.key); 144 | values.push(snapshot); 145 | }); 146 | 147 | return { 148 | keys, 149 | values, 150 | }; 151 | }; 152 | 153 | const addChild = ( 154 | currentState: KeyValueState, 155 | snapshot: DataSnapshot, 156 | previousKey?: string | null 157 | ): KeyValueState => { 158 | if (!snapshot.key) { 159 | return currentState; 160 | } 161 | 162 | const { keys, values } = currentState; 163 | if (!previousKey) { 164 | // The child has been added to the start of the list 165 | return { 166 | keys: keys ? [snapshot.key, ...keys] : [snapshot.key], 167 | values: values ? [snapshot, ...values] : [snapshot], 168 | }; 169 | } 170 | // Establish the index for the previous child in the list 171 | const index = keys ? keys.indexOf(previousKey) : 0; 172 | // Insert the item after the previous child 173 | return { 174 | keys: keys 175 | ? [...keys.slice(0, index + 1), snapshot.key, ...keys.slice(index + 1)] 176 | : [snapshot.key], 177 | values: values 178 | ? [...values.slice(0, index + 1), snapshot, ...values.slice(index + 1)] 179 | : [snapshot], 180 | }; 181 | }; 182 | 183 | const changeChild = ( 184 | currentState: KeyValueState, 185 | snapshot: DataSnapshot 186 | ): KeyValueState => { 187 | if (!snapshot.key) { 188 | return currentState; 189 | } 190 | const { keys, values } = currentState; 191 | const index = keys ? keys.indexOf(snapshot.key) : 0; 192 | return { 193 | ...currentState, 194 | values: values 195 | ? [...values.slice(0, index), snapshot, ...values.slice(index + 1)] 196 | : [snapshot], 197 | }; 198 | }; 199 | 200 | const removeChild = ( 201 | currentState: KeyValueState, 202 | snapshot: DataSnapshot 203 | ): KeyValueState => { 204 | if (!snapshot.key) { 205 | return currentState; 206 | } 207 | 208 | const { keys, values } = currentState; 209 | const index = keys ? keys.indexOf(snapshot.key) : 0; 210 | return { 211 | keys: keys ? [...keys.slice(0, index), ...keys.slice(index + 1)] : [], 212 | values: values 213 | ? [...values.slice(0, index), ...values.slice(index + 1)] 214 | : [], 215 | }; 216 | }; 217 | 218 | const moveChild = ( 219 | currentState: KeyValueState, 220 | snapshot: DataSnapshot, 221 | previousKey?: string | null 222 | ): KeyValueState => { 223 | // Remove the child from it's previous location 224 | const tempValue = removeChild(currentState, snapshot); 225 | // Add the child into it's new location 226 | return addChild(tempValue, snapshot, previousKey); 227 | }; 228 | 229 | export default () => useReducer(listReducer, initialState); 230 | -------------------------------------------------------------------------------- /database/index.ts: -------------------------------------------------------------------------------- 1 | export { useList, useListKeys, useListVals } from './useList'; 2 | export { useObject, useObjectVal } from './useObject'; 3 | export { 4 | ListHook, 5 | ListKeysHook, 6 | ListValsHook, 7 | ObjectHook, 8 | ObjectValHook, 9 | } from './types'; 10 | -------------------------------------------------------------------------------- /database/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-hooks/database", 3 | "main": "dist/index.cjs.js", 4 | "module": "dist/index.esm.js", 5 | "typings": "dist/database/index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /database/types.ts: -------------------------------------------------------------------------------- 1 | import { DatabaseReference, DataSnapshot } from 'firebase/database'; 2 | import { LoadingHook } from '../util'; 3 | 4 | export type Val< 5 | T, 6 | KeyField extends string = '', 7 | RefField extends string = '' 8 | > = T & Record & Record; 9 | 10 | export type ObjectHook = LoadingHook; 11 | export type ObjectValHook< 12 | T, 13 | KeyField extends string = '', 14 | RefField extends string = '' 15 | > = LoadingHook, Error>; 16 | 17 | export type ListHook = LoadingHook; 18 | export type ListKeysHook = LoadingHook; 19 | export type ListValsHook< 20 | T, 21 | KeyField extends string = '', 22 | RefField extends string = '' 23 | > = LoadingHook[], Error>; 24 | -------------------------------------------------------------------------------- /database/useList.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useMemo } from 'react'; 2 | import { snapshotToData, ValOptions } from './helpers'; 3 | import useListReducer from './helpers/useListReducer'; 4 | import { ListHook, ListKeysHook, ListValsHook, Val } from './types'; 5 | import { useIsEqualRef } from '../util'; 6 | import { 7 | DataSnapshot, 8 | Query, 9 | onChildAdded as firebaseOnChildAdded, 10 | onChildChanged as firebaseOnChildChanged, 11 | onChildMoved as firebaseOnChildMoved, 12 | onChildRemoved as firebaseOnChildRemoved, 13 | onValue as firebaseOnValue, 14 | off, 15 | } from 'firebase/database'; 16 | 17 | export const useList = (query?: Query | null): ListHook => { 18 | const [state, dispatch] = useListReducer(); 19 | 20 | const queryRef = useIsEqualRef(query, () => dispatch({ type: 'reset' })); 21 | const ref: Query | null | undefined = queryRef.current; 22 | 23 | useEffect(() => { 24 | if (!ref) { 25 | dispatch({ type: 'empty' }); 26 | return; 27 | } 28 | 29 | const onChildAdded = ( 30 | snapshot: DataSnapshot | null, 31 | previousKey?: string | null 32 | ) => { 33 | dispatch({ type: 'add', previousKey, snapshot }); 34 | }; 35 | 36 | const onChildChanged = (snapshot: DataSnapshot | null) => { 37 | dispatch({ type: 'change', snapshot }); 38 | }; 39 | 40 | const onChildMoved = ( 41 | snapshot: DataSnapshot | null, 42 | previousKey?: string | null 43 | ) => { 44 | dispatch({ type: 'move', previousKey, snapshot }); 45 | }; 46 | 47 | const onChildRemoved = (snapshot: DataSnapshot | null) => { 48 | dispatch({ type: 'remove', snapshot }); 49 | }; 50 | 51 | const onError = (error: Error) => { 52 | dispatch({ type: 'error', error }); 53 | }; 54 | 55 | const onValue = (snapshots: DataSnapshot[] | null) => { 56 | dispatch({ type: 'value', snapshots }); 57 | }; 58 | 59 | let childAddedHandler: ReturnType | undefined; 60 | const onInitialLoad = (snapshot: DataSnapshot) => { 61 | const snapshotVal = snapshot.val(); 62 | let childrenToProcess = snapshotVal 63 | ? Object.keys(snapshot.val()).length 64 | : 0; 65 | 66 | // If the list is empty then initialise the hook and use the default `onChildAdded` behaviour 67 | if (childrenToProcess === 0) { 68 | childAddedHandler = firebaseOnChildAdded(ref, onChildAdded, onError); 69 | onValue([]); 70 | } else { 71 | // Otherwise, we load the first batch of children all to reduce re-renders 72 | const children: DataSnapshot[] = []; 73 | 74 | const onChildAddedWithoutInitialLoad = ( 75 | addedChild: DataSnapshot, 76 | previousKey?: string | null 77 | ) => { 78 | if (childrenToProcess > 0) { 79 | childrenToProcess--; 80 | children.push(addedChild); 81 | 82 | if (childrenToProcess === 0) { 83 | onValue(children); 84 | } 85 | 86 | return; 87 | } 88 | 89 | onChildAdded(addedChild, previousKey); 90 | }; 91 | 92 | childAddedHandler = firebaseOnChildAdded( 93 | ref, 94 | onChildAddedWithoutInitialLoad, 95 | onError 96 | ); 97 | } 98 | }; 99 | 100 | firebaseOnValue(ref, onInitialLoad, onError, { onlyOnce: true }); 101 | const childChangedHandler = firebaseOnChildChanged( 102 | ref, 103 | onChildChanged, 104 | onError 105 | ); 106 | const childMovedHandler = firebaseOnChildMoved(ref, onChildMoved, onError); 107 | const childRemovedHandler = firebaseOnChildRemoved( 108 | ref, 109 | onChildRemoved, 110 | onError 111 | ); 112 | 113 | return () => { 114 | off(ref, 'child_added', childAddedHandler); 115 | off(ref, 'child_changed', childChangedHandler); 116 | off(ref, 'child_moved', childMovedHandler); 117 | off(ref, 'child_removed', childRemovedHandler); 118 | }; 119 | }, [dispatch, ref]); 120 | 121 | const resArray: ListHook = [state.value.values, state.loading, state.error]; 122 | return useMemo(() => resArray, resArray); 123 | }; 124 | 125 | export const useListKeys = (query?: Query | null): ListKeysHook => { 126 | const [snapshots, loading, error] = useList(query); 127 | const values = useMemo( 128 | () => 129 | snapshots 130 | ? snapshots.map((snapshot) => snapshot.key as string) 131 | : undefined, 132 | [snapshots] 133 | ); 134 | const resArray: ListKeysHook = [values, loading, error]; 135 | 136 | return useMemo(() => resArray, resArray); 137 | }; 138 | 139 | export const useListVals = < 140 | T, 141 | KeyField extends string = '', 142 | RefField extends string = '' 143 | >( 144 | query?: Query | null, 145 | options?: ValOptions 146 | ): ListValsHook => { 147 | const keyField = options ? options.keyField : undefined; 148 | const refField = options ? options.refField : undefined; 149 | const transform = options ? options.transform : undefined; 150 | const [snapshots, loading, error] = useList(query); 151 | const values = useMemo( 152 | () => 153 | (snapshots 154 | ? snapshots.map((snapshot) => 155 | snapshotToData(snapshot, keyField, refField, transform) 156 | ) 157 | : undefined) as Val[], 158 | [snapshots, keyField, refField, transform] 159 | ); 160 | 161 | const resArray: ListValsHook = [ 162 | values, 163 | loading, 164 | error, 165 | ]; 166 | return useMemo(() => resArray, resArray); 167 | }; 168 | -------------------------------------------------------------------------------- /database/useObject.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useMemo } from 'react'; 2 | import { snapshotToData, ValOptions } from './helpers'; 3 | import { ObjectHook, ObjectValHook, Val } from './types'; 4 | import { useIsEqualRef, useLoadingValue } from '../util'; 5 | import { DataSnapshot, off, onValue, Query } from 'firebase/database'; 6 | 7 | export const useObject = (query?: Query | null): ObjectHook => { 8 | const { error, loading, reset, setError, setValue, value } = useLoadingValue< 9 | DataSnapshot, 10 | Error 11 | >(); 12 | const ref = useIsEqualRef(query, reset); 13 | 14 | useEffect(() => { 15 | const query = ref.current; 16 | if (!query) { 17 | setValue(undefined); 18 | return; 19 | } 20 | 21 | onValue(query, setValue, setError); 22 | 23 | return () => { 24 | off(query, 'value', setValue); 25 | }; 26 | }, [ref.current]); 27 | 28 | const resArray: ObjectHook = [value, loading, error]; 29 | return useMemo(() => resArray, resArray); 30 | }; 31 | 32 | export const useObjectVal = < 33 | T, 34 | KeyField extends string = '', 35 | RefField extends string = '' 36 | >( 37 | query?: Query | null, 38 | options?: ValOptions 39 | ): ObjectValHook => { 40 | const keyField = options ? options.keyField : undefined; 41 | const refField = options ? options.refField : undefined; 42 | const transform = options ? options.transform : undefined; 43 | const [snapshot, loading, error] = useObject(query); 44 | const value = useMemo( 45 | () => 46 | (snapshot 47 | ? snapshotToData(snapshot, keyField, refField, transform) 48 | : undefined) as Val, 49 | [snapshot, keyField, refField, transform] 50 | ); 51 | 52 | const resArray: ObjectValHook = [ 53 | value, 54 | loading, 55 | error, 56 | ]; 57 | return useMemo(() => resArray, resArray); 58 | }; 59 | -------------------------------------------------------------------------------- /firestore/README.md: -------------------------------------------------------------------------------- 1 | # React Firebase Hooks - Cloud Firestore 2 | 3 | React Firebase Hooks provides convenience listeners for Collections and Documents stored with Cloud Firestore. The hooks wrap around the `firestore.onSnapshot(...)` method. 4 | 5 | In addition to returning the snapshot value, the hooks provide an `error` and `loading` property 6 | to give a complete lifecycle for loading and listening to Cloud Firestore. 7 | 8 | There are 2 variants of each hook: 9 | 10 | - `useX` which subscribes to the underlying Collection or Document and listens for changes 11 | - `useXOnce` which reads the current value of the Collection or Document 12 | 13 | All hooks can be imported from `react-firebase-hooks/firestore`, e.g. 14 | 15 | ```js 16 | import { useCollection } from 'react-firebase-hooks/firestore'; 17 | ``` 18 | 19 | List of Cloud Firestore hooks: 20 | 21 | - [React Firebase Hooks - Cloud Firestore](#react-firebase-hooks---cloud-firestore) 22 | - [useCollection](#usecollection) 23 | - [Full example](#full-example) 24 | - [useCollectionOnce](#usecollectiononce) 25 | - [useCollectionData](#usecollectiondata) 26 | - [useCollectionDataOnce](#usecollectiondataonce) 27 | - [useDocument](#usedocument) 28 | - [Full example](#full-example-1) 29 | - [useDocumentOnce](#usedocumentonce) 30 | - [useDocumentData](#usedocumentdata) 31 | - [useDocumentDataOnce](#usedocumentdataonce) 32 | - [Transforming data](#transforming-data) 33 | 34 | Additional functionality: 35 | 36 | - [Transforming data](#transforming-data) 37 | 38 | ### useCollection 39 | 40 | ```js 41 | const [snapshot, loading, error] = useCollection(query, options); 42 | ``` 43 | 44 | Retrieve and monitor a collection value in Cloud Firestore. 45 | 46 | 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. 47 | 48 | The `useCollection` hook takes the following parameters: 49 | 50 | - `query`: (optional) `firestore.Query` for the data you would like to load 51 | - `options`: (optional) `Object` with the following parameters: 52 | - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the query is loaded 53 | 54 | Returns: 55 | 56 | - `snapshot`: a `firestore.QuerySnapshot`, or `undefined` if no query is supplied 57 | - `loading`: a `boolean` to indicate if the data is still being loaded 58 | - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error 59 | 60 | #### Full example 61 | 62 | ```js 63 | import { getFirestore, collection } from 'firebase/firestore'; 64 | import { useCollection } from 'react-firebase-hooks/firestore'; 65 | 66 | const FirestoreCollection = () => { 67 | const [value, loading, error] = useCollection( 68 | collection(getFirestore(firebaseApp), 'hooks'), 69 | { 70 | snapshotListenOptions: { includeMetadataChanges: true }, 71 | } 72 | ); 73 | return ( 74 |
75 |

76 | {error && Error: {JSON.stringify(error)}} 77 | {loading && Collection: Loading...} 78 | {value && ( 79 | 80 | Collection:{' '} 81 | {value.docs.map((doc) => ( 82 | 83 | {JSON.stringify(doc.data())},{' '} 84 | 85 | ))} 86 | 87 | )} 88 |

89 |
90 | ); 91 | }; 92 | ``` 93 | 94 | ### useCollectionOnce 95 | 96 | ```js 97 | const [snapshot, loading, error] = useCollectionOnce(query, options); 98 | ``` 99 | 100 | Retrieve the current value of the `firestore.Query`. 101 | 102 | The `useCollectionOnce` hook takes the following parameters: 103 | 104 | - `query`: (optional) `firestore.Query` for the data you would like to load 105 | - `options`: (optional) `Object` with the following parameters: 106 | - `getOptions`: (optional) `Object` to customise how the collection is loaded 107 | - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cache. 108 | 109 | Returns: 110 | 111 | - `snapshot`: a `firestore.QuerySnapshot`, or `undefined` if no query is supplied 112 | - `loading`: a `boolean` to indicate if the data is still being loaded 113 | - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error 114 | 115 | ### useCollectionData 116 | 117 | ```js 118 | const [values, loading, error, snapshot] = 119 | useCollectionData < T > (query, options); 120 | ``` 121 | 122 | As `useCollection`, but this hook extracts a typed list of the `firestore.QuerySnapshot.docs` values, rather than the 123 | `firestore.QuerySnapshot` itself. 124 | 125 | The `useCollectionData` hook takes the following parameters: 126 | 127 | - `query`: (optional) `firestore.Query` for the data you would like to load 128 | - `options`: (optional) `Object` with the following parameters: 129 | - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the collection is loaded 130 | - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots 131 | 132 | Returns: 133 | 134 | - `values`: an array of `T`, or `undefined` if no query is supplied 135 | - `loading`: a `boolean` to indicate if the data is still being loaded 136 | - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error 137 | - `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 138 | 139 | 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. 140 | 141 | ### useCollectionDataOnce 142 | 143 | ```js 144 | const [values, loading, error, snapshot] = 145 | useCollectionDataOnce < T > (query, options); 146 | ``` 147 | 148 | As `useCollectionData`, but this hook will only read the current value of the `firestore.Query`. 149 | 150 | The `useCollectionDataOnce` hook takes the following parameters: 151 | 152 | - `query`: (optional) `firestore.Query` for the data you would like to load 153 | - `options`: (optional) `Object` with the following parameters: 154 | - `getOptions`: (optional) `Object` to customise how the collection is loaded 155 | - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cache. 156 | - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots 157 | 158 | Returns: 159 | 160 | - `values`: an array of `T`, or `undefined` if no query is supplied 161 | - `loading`: a `boolean` to indicate if the data is still being loaded 162 | - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error 163 | - `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 164 | 165 | 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. 166 | 167 | ### useDocument 168 | 169 | ```js 170 | const [snapshot, loading, error] = useDocument(reference, options); 171 | ``` 172 | 173 | Retrieve and monitor a document value in Cloud Firestore. 174 | 175 | The `useDocument` hook takes the following parameters: 176 | 177 | - `reference`: (optional) `firestore.DocumentReference` for the data you would like to load 178 | - `options`: (optional) `Object` with the following parameters: 179 | - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the query is loaded 180 | 181 | Returns: 182 | 183 | - `snapshot`: a `firestore.DocumentSnapshot`, or `undefined` if no query is supplied 184 | - `loading`: a `boolean` to indicate if the data is still being loaded 185 | - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error 186 | 187 | #### Full example 188 | 189 | ```js 190 | import { getFirestore, doc } from 'firebase/firestore'; 191 | import { useDocument } from 'react-firebase-hooks/firestore'; 192 | 193 | const FirestoreDocument = () => { 194 | const [value, loading, error] = useDocument( 195 | doc(getFirestore(firebaseApp), 'hooks', 'nBShXiRGFAhuiPfBaGpt'), 196 | { 197 | snapshotListenOptions: { includeMetadataChanges: true }, 198 | } 199 | ); 200 | return ( 201 |
202 |

203 | {error && Error: {JSON.stringify(error)}} 204 | {loading && Document: Loading...} 205 | {value && Document: {JSON.stringify(value.data())}} 206 |

207 |
208 | ); 209 | }; 210 | ``` 211 | 212 | ### useDocumentOnce 213 | 214 | ```js 215 | const [snapshot, loading, error, reload] = useDocumentOnce(reference, options); 216 | ``` 217 | 218 | Retrieve the current value of the `firestore.DocumentReference`. 219 | 220 | The `useDocumentOnce` hook takes the following parameters: 221 | 222 | - `reference`: (optional) `firestore.DocumentReference` for the data you would like to load 223 | - `options`: (optional) `Object` with the following parameters: 224 | - `getOptions`: (optional) `Object` to customise how the collection is loaded 225 | - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cache. 226 | 227 | Returns: 228 | 229 | - `snapshot`: a `firestore.DocumentSnapshot`, or `undefined` if no reference is supplied 230 | - `loading`: a `boolean` to indicate if the data is still being loaded 231 | - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error 232 | - `reload()`: a function that can be called to trigger a reload of the data 233 | 234 | ### useDocumentData 235 | 236 | ```js 237 | const [value, loading, error, snapshot] = 238 | useDocumentData < T > (reference, options); 239 | ``` 240 | 241 | As `useDocument`, but this hook extracts the typed contents of `firestore.DocumentSnapshot.data()`, rather than the 242 | `firestore.DocumentSnapshot` itself. 243 | 244 | The `useDocumentData` hook takes the following parameters: 245 | 246 | - `reference`: (optional) `firestore.DocumentReference` for the data you would like to load 247 | - `options`: (optional) `Object` with the following parameters: 248 | - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the collection is loaded 249 | - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots 250 | 251 | Returns: 252 | 253 | - `value`: `T`, or `undefined` if no query is supplied 254 | - `loading`: a `boolean` to indicate if the data is still being loaded 255 | - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error 256 | - `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 257 | 258 | 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. 259 | 260 | ### useDocumentDataOnce 261 | 262 | ```js 263 | const [value, loading, error, snapshot, reload] = 264 | useDocumentDataOnce < T > (reference, options); 265 | ``` 266 | 267 | As `useDocument`, but this hook will only read the current value of the `firestore.DocumentReference`. 268 | 269 | The `useDocumentDataOnce` hook takes the following parameters: 270 | 271 | - `reference`: (optional) `firestore.DocumentReference` for the data you would like to load 272 | - `options`: (optional) `Object` with the following parameters: 273 | - `getOptions`: (optional) `Object` to customise how the collection is loaded 274 | - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cach 275 | - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots 276 | 277 | Returns: 278 | 279 | - `value`: `T`, or `undefined` if no query is supplied 280 | - `loading`: a `boolean` to indicate if the data is still being loaded 281 | - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error 282 | - `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 283 | - `reload()`: a function that can be called to trigger a reload of the data 284 | 285 | 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. 286 | 287 | ## Transforming data 288 | 289 | 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 290 | 291 | > NOTE: This replaces the `transform`, `idField` and `refField` options that were available in `react-firebase-hooks` v4 and earlier. 292 | 293 | ### Example 294 | 295 | ```js 296 | type Post = { 297 | author: string, 298 | id: string, 299 | ref: DocumentReference, 300 | title: string, 301 | }; 302 | 303 | const postConverter: FirestoreDataConverter = { 304 | toFirestore(post: WithFieldValue): DocumentData { 305 | return { author: post.author, title: post.title }; 306 | }, 307 | fromFirestore( 308 | snapshot: QueryDocumentSnapshot, 309 | options: SnapshotOptions 310 | ): Post { 311 | const data = snapshot.data(options); 312 | return { 313 | author: data.author, 314 | id: snapshot.id, 315 | ref: snapshot.ref, 316 | title: data.title, 317 | }; 318 | }, 319 | }; 320 | 321 | const ref = collection(firestore, 'posts').withConverter(postConverter); 322 | const [data, loading, error] = useCollectionData(ref); 323 | ``` 324 | -------------------------------------------------------------------------------- /firestore/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CollectionReference, 3 | DocumentReference, 4 | Query, 5 | queryEqual, 6 | refEqual, 7 | } from 'firebase/firestore'; 8 | import { RefHook, useComparatorRef } from '../../util'; 9 | 10 | const isRefEqual = < 11 | T extends DocumentReference | CollectionReference 12 | >( 13 | v1: T | null | undefined, 14 | v2: T | null | undefined 15 | ): boolean => { 16 | const bothNull: boolean = !v1 && !v2; 17 | const equal: boolean = !!v1 && !!v2 && refEqual(v1, v2); 18 | return bothNull || equal; 19 | }; 20 | 21 | export const useIsFirestoreRefEqual = < 22 | T extends DocumentReference | CollectionReference 23 | >( 24 | value: T | null | undefined, 25 | onChange?: () => void 26 | ): RefHook => { 27 | return useComparatorRef(value, isRefEqual, onChange); 28 | }; 29 | 30 | const isQueryEqual = >( 31 | v1: T | null | undefined, 32 | v2: T | null | undefined 33 | ): boolean => { 34 | const bothNull: boolean = !v1 && !v2; 35 | const equal: boolean = !!v1 && !!v2 && queryEqual(v1, v2); 36 | return bothNull || equal; 37 | }; 38 | 39 | export const useIsFirestoreQueryEqual = >( 40 | value: T | null | undefined, 41 | onChange?: () => void 42 | ): RefHook => { 43 | return useComparatorRef(value, isQueryEqual, onChange); 44 | }; 45 | -------------------------------------------------------------------------------- /firestore/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | useCollection, 3 | useCollectionOnce, 4 | useCollectionData, 5 | useCollectionDataOnce, 6 | } from './useCollection'; 7 | export { 8 | useDocument, 9 | useDocumentData, 10 | useDocumentOnce, 11 | useDocumentDataOnce, 12 | } from './useDocument'; 13 | export { 14 | CollectionHook, 15 | CollectionDataHook, 16 | DocumentHook, 17 | DocumentDataHook, 18 | } from './types'; 19 | -------------------------------------------------------------------------------- /firestore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-hooks/firestore", 3 | "main": "dist/index.cjs.js", 4 | "module": "dist/index.esm.js", 5 | "typings": "dist/firestore/index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /firestore/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DocumentData, 3 | DocumentSnapshot, 4 | FirestoreError, 5 | QuerySnapshot, 6 | SnapshotListenOptions, 7 | SnapshotOptions, 8 | } from 'firebase/firestore'; 9 | import { LoadingHook } from '../util'; 10 | 11 | export type IDOptions = { 12 | snapshotOptions?: SnapshotOptions; 13 | }; 14 | export type Options = { 15 | snapshotListenOptions?: SnapshotListenOptions; 16 | }; 17 | export type DataOptions = Options & IDOptions; 18 | export type OnceOptions = { 19 | getOptions?: GetOptions; 20 | }; 21 | export type GetOptions = { 22 | source?: 'default' | 'server' | 'cache'; 23 | }; 24 | export type OnceDataOptions = OnceOptions & IDOptions; 25 | 26 | export type CollectionHook = LoadingHook< 27 | QuerySnapshot, 28 | FirestoreError 29 | >; 30 | export type CollectionOnceHook = [ 31 | ...CollectionHook, 32 | () => Promise 33 | ]; 34 | export type CollectionDataHook = [ 35 | ...LoadingHook, 36 | QuerySnapshot | undefined 37 | ]; 38 | export type CollectionDataOnceHook = [ 39 | ...CollectionDataHook, 40 | () => Promise 41 | ]; 42 | 43 | export type DocumentHook = LoadingHook< 44 | DocumentSnapshot, 45 | FirestoreError 46 | >; 47 | export type DocumentOnceHook = [ 48 | ...DocumentHook, 49 | () => Promise 50 | ]; 51 | export type DocumentDataHook = [ 52 | ...LoadingHook, 53 | DocumentSnapshot | undefined 54 | ]; 55 | export type DocumentDataOnceHook = [ 56 | ...DocumentDataHook, 57 | () => Promise 58 | ]; 59 | -------------------------------------------------------------------------------- /firestore/useCollection.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DocumentData, 3 | FirestoreError, 4 | getDocs, 5 | getDocsFromCache, 6 | getDocsFromServer, 7 | onSnapshot, 8 | Query, 9 | QuerySnapshot, 10 | SnapshotOptions, 11 | } from 'firebase/firestore'; 12 | import { useEffect, useMemo } from 'react'; 13 | import { useLoadingValue } from '../util'; 14 | import { useIsFirestoreQueryEqual } from './helpers'; 15 | import { 16 | CollectionDataHook, 17 | CollectionDataOnceHook, 18 | CollectionHook, 19 | CollectionOnceHook, 20 | DataOptions, 21 | GetOptions, 22 | OnceDataOptions, 23 | OnceOptions, 24 | Options, 25 | } from './types'; 26 | 27 | export const useCollection = ( 28 | query?: Query | null, 29 | options?: Options 30 | ): CollectionHook => { 31 | const { error, loading, reset, setError, setValue, value } = useLoadingValue< 32 | QuerySnapshot, 33 | FirestoreError 34 | >(); 35 | const ref = useIsFirestoreQueryEqual>(query, reset); 36 | 37 | useEffect(() => { 38 | if (!ref.current) { 39 | setValue(undefined); 40 | return; 41 | } 42 | const unsubscribe = options?.snapshotListenOptions 43 | ? onSnapshot( 44 | ref.current, 45 | options.snapshotListenOptions, 46 | setValue, 47 | setError 48 | ) 49 | : onSnapshot(ref.current, setValue, setError); 50 | 51 | return () => { 52 | unsubscribe(); 53 | }; 54 | }, [ref.current]); 55 | 56 | const resArray: CollectionHook = [ 57 | value as QuerySnapshot, 58 | loading, 59 | error, 60 | ]; 61 | return useMemo(() => resArray, resArray); 62 | }; 63 | 64 | export const useCollectionOnce = ( 65 | query?: Query | null, 66 | options?: OnceOptions 67 | ): CollectionOnceHook => { 68 | const { error, loading, reset, setError, setValue, value } = useLoadingValue< 69 | QuerySnapshot, 70 | FirestoreError 71 | >(); 72 | let effectActive = true; 73 | const ref = useIsFirestoreQueryEqual>(query, reset); 74 | 75 | const loadData = async ( 76 | query?: Query | null, 77 | options?: Options & OnceOptions 78 | ) => { 79 | if (!query) { 80 | setValue(undefined); 81 | return; 82 | } 83 | const get = getDocsFnFromGetOptions(options?.getOptions); 84 | 85 | try { 86 | const result = await get(query); 87 | if (effectActive) { 88 | setValue(result); 89 | } 90 | } catch (error) { 91 | if (effectActive) { 92 | setError(error as FirestoreError); 93 | } 94 | } 95 | }; 96 | 97 | useEffect(() => { 98 | loadData(ref.current, options); 99 | 100 | return () => { 101 | effectActive = false; 102 | }; 103 | }, [ref.current]); 104 | 105 | const resArray: CollectionOnceHook = [ 106 | value as QuerySnapshot, 107 | loading, 108 | error, 109 | () => loadData(ref.current, options), 110 | ]; 111 | return useMemo(() => resArray, resArray); 112 | }; 113 | 114 | export const useCollectionData = ( 115 | query?: Query | null, 116 | options?: DataOptions 117 | ): CollectionDataHook => { 118 | const snapshotOptions = options?.snapshotOptions; 119 | const [snapshots, loading, error] = useCollection(query, options); 120 | const values = getValuesFromSnapshots(snapshots, snapshotOptions); 121 | const resArray: CollectionDataHook = [values, loading, error, snapshots]; 122 | return useMemo(() => resArray, resArray); 123 | }; 124 | 125 | export const useCollectionDataOnce = ( 126 | query?: Query | null, 127 | options?: OnceDataOptions 128 | ): CollectionDataOnceHook => { 129 | const snapshotOptions = options?.snapshotOptions; 130 | const [snapshots, loading, error, loadData] = useCollectionOnce( 131 | query, 132 | options 133 | ); 134 | const values = getValuesFromSnapshots(snapshots, snapshotOptions); 135 | const resArray: CollectionDataOnceHook = [ 136 | values, 137 | loading, 138 | error, 139 | snapshots, 140 | loadData, 141 | ]; 142 | return useMemo(() => resArray, resArray); 143 | }; 144 | 145 | const getValuesFromSnapshots = ( 146 | snapshots?: QuerySnapshot, 147 | options?: SnapshotOptions 148 | ) => { 149 | return useMemo(() => snapshots?.docs.map((doc) => doc.data(options)) as T[], [ 150 | snapshots, 151 | options, 152 | ]); 153 | }; 154 | 155 | const getDocsFnFromGetOptions = ( 156 | { source }: GetOptions = { source: 'default' } 157 | ) => { 158 | switch (source) { 159 | default: 160 | case 'default': 161 | return getDocs; 162 | case 'cache': 163 | return getDocsFromCache; 164 | case 'server': 165 | return getDocsFromServer; 166 | } 167 | }; 168 | -------------------------------------------------------------------------------- /firestore/useDocument.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DocumentData, 3 | DocumentReference, 4 | DocumentSnapshot, 5 | FirestoreError, 6 | getDoc, 7 | getDocFromCache, 8 | getDocFromServer, 9 | onSnapshot, 10 | } from 'firebase/firestore'; 11 | import { useEffect, useMemo } from 'react'; 12 | import { useLoadingValue } from '../util'; 13 | import { useIsFirestoreRefEqual } from './helpers'; 14 | import { 15 | DataOptions, 16 | DocumentDataHook, 17 | DocumentDataOnceHook, 18 | DocumentHook, 19 | DocumentOnceHook, 20 | GetOptions, 21 | OnceDataOptions, 22 | OnceOptions, 23 | Options, 24 | } from './types'; 25 | 26 | export const useDocument = ( 27 | docRef?: DocumentReference | null, 28 | options?: Options 29 | ): DocumentHook => { 30 | const { error, loading, reset, setError, setValue, value } = useLoadingValue< 31 | DocumentSnapshot, 32 | FirestoreError 33 | >(); 34 | const ref = useIsFirestoreRefEqual>(docRef, reset); 35 | 36 | useEffect(() => { 37 | if (!ref.current) { 38 | setValue(undefined); 39 | return; 40 | } 41 | const unsubscribe = options?.snapshotListenOptions 42 | ? onSnapshot( 43 | ref.current, 44 | options.snapshotListenOptions, 45 | setValue, 46 | setError 47 | ) 48 | : onSnapshot(ref.current, setValue, setError); 49 | 50 | return () => { 51 | unsubscribe(); 52 | }; 53 | }, [ref.current]); 54 | 55 | const resArray: DocumentHook = [ 56 | value as DocumentSnapshot, 57 | loading, 58 | error, 59 | ]; 60 | return useMemo(() => resArray, resArray); 61 | }; 62 | 63 | export const useDocumentOnce = ( 64 | docRef?: DocumentReference | null, 65 | options?: OnceOptions 66 | ): DocumentOnceHook => { 67 | const { error, loading, reset, setError, setValue, value } = useLoadingValue< 68 | DocumentSnapshot, 69 | FirestoreError 70 | >(); 71 | let effectActive = true; 72 | const ref = useIsFirestoreRefEqual>(docRef, reset); 73 | 74 | const loadData = async ( 75 | reference?: DocumentReference | null, 76 | options?: OnceOptions 77 | ) => { 78 | if (!reference) { 79 | setValue(undefined); 80 | return; 81 | } 82 | const get = getDocFnFromGetOptions(options?.getOptions); 83 | 84 | try { 85 | const result = await get(reference); 86 | if (effectActive) { 87 | setValue(result); 88 | } 89 | } catch (error) { 90 | if (effectActive) { 91 | setError(error as FirestoreError); 92 | } 93 | } 94 | }; 95 | 96 | useEffect(() => { 97 | if (!ref.current) { 98 | setValue(undefined); 99 | return; 100 | } 101 | 102 | loadData(ref.current, options); 103 | 104 | return () => { 105 | effectActive = false; 106 | }; 107 | }, [ref.current]); 108 | 109 | const resArray: DocumentOnceHook = [ 110 | value as DocumentSnapshot, 111 | loading, 112 | error, 113 | () => loadData(ref.current, options), 114 | ]; 115 | return useMemo(() => resArray, resArray); 116 | }; 117 | 118 | export const useDocumentData = ( 119 | docRef?: DocumentReference | null, 120 | options?: DataOptions 121 | ): DocumentDataHook => { 122 | const snapshotOptions = options?.snapshotOptions; 123 | const [snapshot, loading, error] = useDocument(docRef, options); 124 | const value = useMemo(() => snapshot?.data(snapshotOptions) as T, [ 125 | snapshot, 126 | snapshotOptions, 127 | ]); 128 | 129 | const resArray: DocumentDataHook = [value, loading, error, snapshot]; 130 | return useMemo(() => resArray, resArray); 131 | }; 132 | 133 | export const useDocumentDataOnce = ( 134 | docRef?: DocumentReference | null, 135 | options?: OnceDataOptions 136 | ): DocumentDataOnceHook => { 137 | const snapshotOptions = options?.snapshotOptions; 138 | const [snapshot, loading, error, loadData] = useDocumentOnce( 139 | docRef, 140 | options 141 | ); 142 | const value = useMemo(() => snapshot?.data(snapshotOptions) as T, [ 143 | snapshot, 144 | snapshotOptions, 145 | ]); 146 | 147 | const resArray: DocumentDataOnceHook = [ 148 | value, 149 | loading, 150 | error, 151 | snapshot, 152 | loadData, 153 | ]; 154 | return useMemo(() => resArray, resArray); 155 | }; 156 | 157 | const getDocFnFromGetOptions = ( 158 | { source }: GetOptions = { source: 'default' } 159 | ) => { 160 | switch (source) { 161 | default: 162 | case 'default': 163 | return getDoc; 164 | case 'cache': 165 | return getDocFromCache; 166 | case 'server': 167 | return getDocFromServer; 168 | } 169 | }; 170 | -------------------------------------------------------------------------------- /functions/README.md: -------------------------------------------------------------------------------- 1 | # React Firebase Hooks - Cloud Functions 2 | 3 | React Firebase Hooks provides a convenience hook for HttpsCallable functions, providing an `error` and `loading` property 4 | to give a complete lifecycle for executing a HttpsCallable function on Firebase Cloud Functions. 5 | 6 | All hooks can be imported from `react-firebase-hooks/functions`, e.g. 7 | 8 | ```js 9 | import { useHttpsCallable } from 'react-firebase-hooks/functions'; 10 | ``` 11 | 12 | List of Cloud Functions hooks: 13 | 14 | - [React Firebase Hooks - Cloud Functions](#react-firebase-hooks---cloud-functions) 15 | - [useHttpsCallable](#usehttpscallable) 16 | - [Full example](#full-example) 17 | 18 | ### useHttpsCallable 19 | 20 | ```js 21 | const [executeCallable, loading, error] = useHttpsCallable(functions, name); 22 | ``` 23 | 24 | Generate a callable function and monitor its execution. 25 | 26 | The `useHttpsCallable` hook takes the following parameters: 27 | 28 | - `functions`: `functions.Functions` instance for your Firebase app 29 | - `name`: A `string` representing the name of the function to call 30 | 31 | Returns: 32 | 33 | - `executeCallable(data)`: a function you can call to execute the HttpsCallable 34 | - `loading`: a `boolean` to indicate if the function is still being executed 35 | - `error`: Any `Error` returned by Firebase when trying to execute the function, or `undefined` if there is no error 36 | 37 | #### Full example 38 | 39 | ```js 40 | import { getFunctions } from 'firebase/functions'; 41 | import { useHttpsCallable } from 'react-firebase-hooks/functions'; 42 | 43 | const HttpsCallable = () => { 44 | const [executeCallable, executing, error] = useHttpsCallable( 45 | getFunctions(firebaseApp), 46 | 'myHttpsCallable' 47 | ); 48 | return ( 49 |
50 |

51 | {error && Error: {JSON.stringify(error)}} 52 | {executing && Function executing...} 53 | 61 |

62 |
63 | ); 64 | }; 65 | ``` 66 | -------------------------------------------------------------------------------- /functions/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | default as useHttpsCallable, 3 | HttpsCallableHook, 4 | } from './useHttpsCallable'; 5 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-hooks/functions", 3 | "main": "dist/index.cjs.js", 4 | "module": "dist/index.esm.js", 5 | "typings": "dist/functions/index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /functions/useHttpsCallable.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Functions, 3 | httpsCallable, 4 | HttpsCallableResult, 5 | } from 'firebase/functions'; 6 | import { useMemo, useState } from 'react'; 7 | 8 | export type HttpsCallableHook = [ 9 | ( 10 | data?: RequestData 11 | ) => Promise | undefined>, 12 | boolean, 13 | Error | undefined 14 | ]; 15 | 16 | export default ( 17 | functions: Functions, 18 | name: string 19 | ): HttpsCallableHook => { 20 | const [error, setError] = useState(); 21 | const [loading, setLoading] = useState(false); 22 | 23 | const callCallable = async ( 24 | data?: RequestData 25 | ): Promise | undefined> => { 26 | const callable = httpsCallable(functions, name); 27 | setLoading(true); 28 | setError(undefined); 29 | try { 30 | return await callable(data); 31 | } catch (err) { 32 | setError(err as Error); 33 | } finally { 34 | setLoading(false); 35 | } 36 | }; 37 | 38 | const resArray: HttpsCallableHook = [ 39 | callCallable, 40 | loading, 41 | error, 42 | ]; 43 | return useMemo>( 44 | () => resArray, 45 | resArray 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /messaging/README.md: -------------------------------------------------------------------------------- 1 | # React Firebase Hooks - Cloud Messaging 2 | 3 | React Firebase Hooks provides a convenience hook for getting a cloud messaging token, providing an `error` and `loading` property 4 | to give a complete lifecycle for accessing the cloud messaging token on Firebase Cloud Messaging. 5 | 6 | All hooks can be imported from `react-firebase-hooks/messaging`, e.g. 7 | 8 | ```js 9 | import { useToken } from 'react-firebase-hooks/messaging'; 10 | ``` 11 | 12 | List of Cloud Messaging hooks: 13 | 14 | - [React Firebase Hooks - Cloud Messaging](#react-firebase-hooks---cloud-messaging) 15 | - [useToken](#usetoken) 16 | - [Full example](#full-example) 17 | 18 | ### useToken 19 | 20 | ```js 21 | const [token, loading, error] = useToken(messaging, vapidKey); 22 | ``` 23 | 24 | Get a token from Firebase Cloud Messaging 25 | 26 | The `useToken` hook takes the following parameters: 27 | 28 | - `messaging`: `messaging.Messaging` instance for your Firebase app 29 | - `vapidKey`: a `string` representing the VAPID key credential needed for Cloud Messaging 30 | 31 | Returns: 32 | 33 | - `token`: a `string` token to use with cloud messaging 34 | - `loading`: a `boolean` to indicate if the function is still being executed 35 | - `error`: Any `Error` returned by Firebase when trying to execute the function, or `undefined` if there is no error 36 | 37 | #### Full example 38 | 39 | ```js 40 | import { getMessaging } from 'firebase/messaging'; 41 | import { useToken } from 'react-firebase-hooks/messaging'; 42 | 43 | const MessagingToken = () => { 44 | const [token, loading, error] = useToken(getMessaging(firebaseApp)); 45 | return ( 46 |
47 |

48 | {error && Error: {JSON.stringify(error)}} 49 | {loading && Loading token...} 50 | {token && Token:{token}} 51 |

52 |
53 | ); 54 | }; 55 | ``` 56 | -------------------------------------------------------------------------------- /messaging/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useToken, TokenHook } from './useToken'; 2 | -------------------------------------------------------------------------------- /messaging/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-hooks/messaging", 3 | "main": "dist/index.cjs.js", 4 | "module": "dist/index.esm.js", 5 | "typings": "dist/messaging/index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /messaging/useToken.ts: -------------------------------------------------------------------------------- 1 | import { Messaging, getToken } from 'firebase/messaging'; 2 | import { useEffect, useMemo } from 'react'; 3 | import { LoadingHook, useLoadingValue } from '../util'; 4 | 5 | export type TokenHook = LoadingHook; 6 | 7 | export default (messaging: Messaging, vapidKey?: string): TokenHook => { 8 | const { error, loading, setError, setValue, value } = useLoadingValue< 9 | string | null, 10 | Error 11 | >(); 12 | 13 | useEffect(() => { 14 | getToken(messaging, { vapidKey }).then(setValue).catch(setError); 15 | }, [messaging]); 16 | 17 | const resArray: TokenHook = [value, loading, error]; 18 | return useMemo(() => resArray, resArray); 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-hooks", 3 | "version": "5.0.3", 4 | "description": "React Hooks for Firebase", 5 | "author": "CS Frequency Limited (https://csfrequency.com)", 6 | "license": "Apache-2.0", 7 | "homepage": "https://github.com/csfrequency/react-firebase-hooks", 8 | "bugs": "https://github.com/csfrequency/react-firebase-hooks/issues", 9 | "keywords": [ 10 | "react", 11 | "hooks", 12 | "firebase" 13 | ], 14 | "files": [ 15 | "auth/dist/*.js", 16 | "auth/dist/auth", 17 | "auth/dist/util", 18 | "auth/package.json", 19 | "database/dist/*.js", 20 | "database/dist/database", 21 | "database/dist/util", 22 | "database/package.json", 23 | "dist/*.js", 24 | "dist/*.js.map", 25 | "firestore/dist/*.js", 26 | "firestore/dist/firestore", 27 | "firestore/dist/util", 28 | "firestore/package.json", 29 | "functions/dist/*.js", 30 | "functions/dist/functions", 31 | "functions/dist/util", 32 | "functions/package.json", 33 | "messaging/dist/*.js", 34 | "messaging/dist/messaging", 35 | "messaging/dist/util", 36 | "messaging/package.json", 37 | "storage/dist/*.js", 38 | "storage/dist/storage", 39 | "storage/dist/util", 40 | "storage/package.json", 41 | "util/dist/*.js", 42 | "util/dist/util", 43 | "util/package.json" 44 | ], 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/csfrequency/react-firebase-hooks.git" 48 | }, 49 | "scripts": { 50 | "build": "npm run clean && rollup -c", 51 | "clean": "rimraf ./dist ./auth/dist ./auth/*.d.ts ./database/dist ./database/*.d.ts ./firestore/dist ./firestore/*.d.ts ./storage/dist ./storage/*.d.ts ./util/*.d.ts", 52 | "dev": "npm run clean && rollup -c -w", 53 | "prepublish": "npm run build", 54 | "prettier": "prettier --check .", 55 | "start": "rollup -c -w" 56 | }, 57 | "main": "dist/index.cjs.js", 58 | "module": "dist/index.esm.js", 59 | "devDependencies": { 60 | "@rollup/plugin-commonjs": "^21.0.1", 61 | "@rollup/plugin-node-resolve": "^13.1.2", 62 | "@types/react": "^17.0.0", 63 | "firebase": "^9.6.5", 64 | "path": "^0.12.7", 65 | "prettier": "2.2.1", 66 | "react": "^17.0.1", 67 | "rimraf": "^2.6.2", 68 | "rollup": "^2.63.0", 69 | "rollup-plugin-copy": "^3.4.0", 70 | "rollup-plugin-terser": "^7.0.2", 71 | "rollup-plugin-typescript2": "^0.31.1", 72 | "tslib": "^2.3.1", 73 | "typescript": "^4.5.4" 74 | }, 75 | "peerDependencies": { 76 | "react": ">= 16.8.0", 77 | "firebase": ">= 9.0.0" 78 | }, 79 | "typings": "index.d.ts" 80 | } 81 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | singleQuote: true, 4 | overrides: [ 5 | { 6 | files: '*.json', 7 | options: { 8 | printWidth: 400, 9 | }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import copy from 'rollup-plugin-copy'; 4 | import resolveModule from '@rollup/plugin-node-resolve'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | import typescript from 'rollup-plugin-typescript2'; 7 | 8 | import pkg from './package.json'; 9 | import authPkg from './auth/package.json'; 10 | import databasePkg from './database/package.json'; 11 | import firestorePkg from './firestore/package.json'; 12 | import functionsPkg from './functions/package.json'; 13 | import messagingPkg from './messaging/package.json'; 14 | import storagePkg from './storage/package.json'; 15 | import utilPkg from './util/package.json'; 16 | 17 | const pkgsByName = { 18 | auth: authPkg, 19 | database: databasePkg, 20 | firestore: firestorePkg, 21 | functions: functionsPkg, 22 | messaging: messagingPkg, 23 | storage: storagePkg, 24 | util: utilPkg, 25 | }; 26 | 27 | const plugins = [ 28 | resolveModule(), 29 | typescript({ 30 | typescript: require('typescript'), 31 | }), 32 | commonjs(), 33 | ]; 34 | 35 | const external = [ 36 | ...Object.keys(pkg.peerDependencies), 37 | 'firebase/auth', 38 | 'firebase/database', 39 | 'firebase/firestore', 40 | 'firebase/functions', 41 | 'firebase/messaging', 42 | 'firebase/storage', 43 | 'firebase/util', 44 | ]; 45 | 46 | const components = [ 47 | 'auth', 48 | 'database', 49 | 'firestore', 50 | 'functions', 51 | 'messaging', 52 | 'storage', 53 | 'util', 54 | ]; 55 | 56 | export default components 57 | .map((component) => { 58 | const pkg = pkgsByName[component]; 59 | return [ 60 | { 61 | input: `${component}/index.ts`, 62 | output: [ 63 | { file: resolve(component, pkg.main), format: 'cjs' }, 64 | { file: resolve(component, pkg.module), format: 'es' }, 65 | ], 66 | plugins, 67 | external, 68 | }, 69 | { 70 | input: `${component}/index.ts`, 71 | output: { 72 | file: `dist/react-firebase-hooks-${component}.js`, 73 | format: 'iife', 74 | sourcemap: true, 75 | extend: true, 76 | name: 'react-firebase-hooks', 77 | globals: { 78 | react: 'react', 79 | }, 80 | }, 81 | plugins: [ 82 | ...plugins, 83 | terser(), 84 | // Copy flow files 85 | copy({ 86 | [`${component}/index.js.flow`]: `${component}/dist/index.cjs.js.flow`, 87 | }), 88 | copy({ 89 | [`${component}/index.js.flow`]: `${component}/dist/index.esm.js.flow`, 90 | }), 91 | ], 92 | external, 93 | }, 94 | ]; 95 | }) 96 | .reduce((a, b) => a.concat(b), []); 97 | -------------------------------------------------------------------------------- /storage/README.md: -------------------------------------------------------------------------------- 1 | # React Firebase Hooks - Cloud Storage 2 | 3 | React Firebase Hooks provides convenience listeners for files stored within 4 | Firebase Cloud Storage. The hooks wrap around the `getDownloadURL(...)` method. 5 | 6 | In addition to returning the download URL, the hooks provide an `error` and `loading` property 7 | to give a complete lifecycle for loading from Cloud Storage. 8 | 9 | All hooks can be imported from `react-firebase-hooks/storage`, e.g. 10 | 11 | ```js 12 | import { useDownloadURL } from 'react-firebase-hooks/storage'; 13 | ``` 14 | 15 | List of Cloud Storage hooks: 16 | 17 | - [useDownloadURL](#usedownloadurl) 18 | - [useUploadFile](#useuploadfile) 19 | 20 | ### useDownloadURL 21 | 22 | ```js 23 | const [downloadUrl, loading, error] = useDownloadURL(reference); 24 | ``` 25 | 26 | Retrieve the download URL for a storage reference. 27 | 28 | The `useDownloadURL` hook takes the following parameters: 29 | 30 | - `reference`: (optional) `storage.Reference` that you would like the download URL for 31 | 32 | Returns: 33 | 34 | - `downloadUrl`: A `string` download URL, or `undefined` if no storage reference is supplied 35 | - `loading`: A `boolean` to indicate whether the the download URL is still being loaded 36 | - `error`: Any `storage.StorageError` returned by Firebase when trying to load the URL, or `undefined` if there is no error 37 | 38 | #### Full example 39 | 40 | ```js 41 | import { getStorage, storageRef } from 'firebase/storage'; 42 | import { useDownloadURL } from 'react-firebase-hooks/storage'; 43 | 44 | const storage = getStorage(firebaseApp); 45 | 46 | const DownloadURL = () => { 47 | const [value, loading, error] = useDownloadURL(ref(storage, 'path/to/file')); 48 | 49 | return ( 50 |
51 |

52 | {error && Error: {error}} 53 | {loading && Download URL: Loading...} 54 | {!loading && value && ( 55 | 56 | Download URL: {value} 57 | 58 | )} 59 |

60 |
61 | ); 62 | }; 63 | ``` 64 | 65 | ### useUploadFile 66 | 67 | ```js 68 | const [uploadFile, uploading, snapshot, error] = useUploadFile(); 69 | ``` 70 | 71 | Upload a file to Firebase storage. 72 | 73 | The `useUploadFile` hook returns the following: 74 | 75 | - `uploadFile(ref, file, metadata)`: A function that can be called to upload a file, with attached metadata, to the storage reference supplied 76 | - `uploading`: A `boolean` to indicate whether the the file is currently being uploaded 77 | - `snapshot`: A `storage.UploadTaskSnapshot` to provide more detailed information about the upload 78 | - `error`: Any `storage.StorageError` returned by Firebase when trying to upload the file, or `undefined` if there is no error 79 | 80 | #### Full example 81 | 82 | ```js 83 | import { getStorage, storageRef } from 'firebase/storage'; 84 | import { useUploadFile } from 'react-firebase-hooks/storage'; 85 | 86 | const storage = getStorage(firebaseApp); 87 | 88 | const UploadFile = () => { 89 | const [uploadFile, uploading, snapshot, error] = useUploadFile(); 90 | const ref = storageRef(storage, 'file.jpg'); 91 | const [selectedFile, setSelectedFile] = useState(); 92 | 93 | const upload = async () => { 94 | if (selectedFile) { 95 | const result = await uploadFile(ref, selectedFile, { 96 | contentType: 'image/jpeg' 97 | }); 98 | alert(`Result: ${JSON.stringify(result)}`); 99 | } 100 | } 101 | 102 | return ( 103 |
104 |

105 | {error && Error: {error.message}} 106 | {uploading && Uploading file...} 107 | {snapshot && Snapshot: {JSON.stringify(snapshot)}} 108 | {selectedFile && Selected file: {selectedFile.name}} 109 | { 112 | const file = e.target.files ? e.target.files[0] : undefined; 113 | setSelectedFile(file); 114 | }} 115 | /> 116 | 117 |

118 |
119 | ) 120 | } 121 | ``` 122 | -------------------------------------------------------------------------------- /storage/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useDownloadURL, DownloadURLHook } from './useDownloadURL'; 2 | export { default as useUploadFile, UploadFileHook } from './useUploadFile'; 3 | -------------------------------------------------------------------------------- /storage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-hooks/storage", 3 | "main": "dist/index.cjs.js", 4 | "module": "dist/index.esm.js", 5 | "typings": "dist/storage/index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /storage/useDownloadURL.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getDownloadURL, 3 | StorageError, 4 | StorageReference, 5 | } from 'firebase/storage'; 6 | import { useEffect } from 'react'; 7 | import { LoadingHook, useComparatorRef, useLoadingValue } from '../util'; 8 | 9 | export type DownloadURLHook = LoadingHook; 10 | 11 | export default (storageRef?: StorageReference | null): DownloadURLHook => { 12 | const { error, loading, reset, setError, setValue, value } = useLoadingValue< 13 | string, 14 | StorageError 15 | >(); 16 | const ref = useComparatorRef(storageRef, isEqual, reset); 17 | 18 | useEffect(() => { 19 | if (!ref.current) { 20 | setValue(undefined); 21 | return; 22 | } 23 | getDownloadURL(ref.current).then(setValue).catch(setError); 24 | }, [ref.current]); 25 | 26 | return [value, loading, error]; 27 | }; 28 | 29 | const isEqual = ( 30 | v1: StorageReference | null | undefined, 31 | v2: StorageReference | null | undefined 32 | ): boolean => { 33 | const bothNull: boolean = !v1 && !v2; 34 | const equal: boolean = !!v1 && !!v2 && v1.fullPath === v2.fullPath; 35 | return bothNull || equal; 36 | }; 37 | -------------------------------------------------------------------------------- /storage/useUploadFile.ts: -------------------------------------------------------------------------------- 1 | import { 2 | StorageError, 3 | StorageReference, 4 | uploadBytesResumable, 5 | UploadMetadata, 6 | UploadResult, 7 | UploadTaskSnapshot, 8 | } from 'firebase/storage'; 9 | import { useMemo, useState } from 'react'; 10 | 11 | export type UploadFileHook = [ 12 | ( 13 | storageRef: StorageReference, 14 | data: Blob | Uint8Array | ArrayBuffer, 15 | metadata?: UploadMetadata | undefined 16 | ) => Promise, 17 | boolean, 18 | UploadTaskSnapshot | undefined, 19 | StorageError | undefined 20 | ]; 21 | 22 | export default (): UploadFileHook => { 23 | const [error, setError] = useState(); 24 | const [uploading, setUploading] = useState(false); 25 | const [snapshot, setSnapshot] = useState(); 26 | 27 | const uploadFile = async ( 28 | storageRef: StorageReference, 29 | data: Blob | Uint8Array | ArrayBuffer, 30 | metadata?: UploadMetadata | undefined 31 | ): Promise => { 32 | return new Promise((resolve, reject) => { 33 | setUploading(true); 34 | setError(undefined); 35 | const uploadTask = uploadBytesResumable(storageRef, data, metadata); 36 | uploadTask.on( 37 | 'state_changed', 38 | (snapshot) => { 39 | setSnapshot(snapshot); 40 | }, 41 | (error) => { 42 | setUploading(false); 43 | setError(error); 44 | resolve(undefined); 45 | }, 46 | () => { 47 | setUploading(false); 48 | setSnapshot(undefined); 49 | resolve({ 50 | metadata: uploadTask.snapshot.metadata, 51 | ref: uploadTask.snapshot.ref, 52 | }); 53 | } 54 | ); 55 | }); 56 | }; 57 | 58 | const resArray: UploadFileHook = [uploadFile, uploading, snapshot, error]; 59 | return useMemo(() => resArray, resArray); 60 | }; 61 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": true, 5 | "importHelpers": true, 6 | "lib": ["es2017", "dom"], 7 | "module": "es6", 8 | "moduleResolution": "node", 9 | "noImplicitAny": false, 10 | "noUnusedLocals": true, 11 | "outDir": "dist", 12 | "sourceMap": true, 13 | "strict": true, 14 | "target": "es5", 15 | "typeRoots": ["../node_modules/@types"] 16 | }, 17 | "exclude": ["dist/**/*"] 18 | } 19 | -------------------------------------------------------------------------------- /util/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useLoadingValue } from './useLoadingValue'; 2 | export * from './refHooks'; 3 | 4 | export type LoadingHook = [T | undefined, boolean, E | undefined]; 5 | -------------------------------------------------------------------------------- /util/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-hooks/util", 3 | "main": "dist/index.cjs.js", 4 | "module": "dist/index.esm.js", 5 | "typings": "dist/util/index.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /util/refHooks.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export type RefHook = { 4 | current: T; 5 | }; 6 | 7 | export const useComparatorRef = ( 8 | value: T | null | undefined, 9 | isEqual: (v1: T | null | undefined, v2: T | null | undefined) => boolean, 10 | onChange?: () => void 11 | ): RefHook => { 12 | const ref = useRef(value); 13 | useEffect(() => { 14 | if (!isEqual(value, ref.current)) { 15 | ref.current = value; 16 | if (onChange) { 17 | onChange(); 18 | } 19 | } 20 | }); 21 | return ref; 22 | }; 23 | 24 | export interface HasIsEqual { 25 | isEqual: (value: T) => boolean; 26 | } 27 | 28 | const isEqual = >( 29 | v1: T | null | undefined, 30 | v2: T | null | undefined 31 | ): boolean => { 32 | const bothNull: boolean = !v1 && !v2; 33 | const equal: boolean = !!v1 && !!v2 && v1.isEqual(v2); 34 | return bothNull || equal; 35 | }; 36 | 37 | export const useIsEqualRef = >( 38 | value: T | null | undefined, 39 | onChange?: () => void 40 | ): RefHook => { 41 | return useComparatorRef(value, isEqual, onChange); 42 | }; 43 | -------------------------------------------------------------------------------- /util/useLoadingValue.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useReducer } from 'react'; 2 | 3 | export type LoadingValue = { 4 | error?: E; 5 | loading: boolean; 6 | reset: () => void; 7 | setError: (error: E) => void; 8 | setValue: (value?: T) => void; 9 | value?: T; 10 | }; 11 | 12 | type ReducerState = { 13 | error?: E; 14 | loading: boolean; 15 | value?: any; 16 | }; 17 | 18 | type ErrorAction = { type: 'error'; error: E }; 19 | type ResetAction = { type: 'reset'; defaultValue?: any }; 20 | type ValueAction = { type: 'value'; value: any }; 21 | type ReducerAction = ErrorAction | ResetAction | ValueAction; 22 | 23 | const defaultState = (defaultValue?: any) => { 24 | return { 25 | loading: defaultValue === undefined || defaultValue === null, 26 | value: defaultValue, 27 | }; 28 | }; 29 | 30 | const reducer = () => ( 31 | state: ReducerState, 32 | action: ReducerAction 33 | ): ReducerState => { 34 | switch (action.type) { 35 | case 'error': 36 | return { 37 | ...state, 38 | error: action.error, 39 | loading: false, 40 | value: undefined, 41 | }; 42 | case 'reset': 43 | return defaultState(action.defaultValue); 44 | case 'value': 45 | return { 46 | ...state, 47 | error: undefined, 48 | loading: false, 49 | value: action.value, 50 | }; 51 | default: 52 | return state; 53 | } 54 | }; 55 | 56 | export default (getDefaultValue?: () => T): LoadingValue => { 57 | const defaultValue = getDefaultValue ? getDefaultValue() : undefined; 58 | const [state, dispatch] = useReducer( 59 | reducer(), 60 | defaultState(defaultValue) 61 | ); 62 | 63 | const reset = () => { 64 | const defaultValue = getDefaultValue ? getDefaultValue() : undefined; 65 | dispatch({ type: 'reset', defaultValue }); 66 | }; 67 | 68 | const setError = (error: E) => { 69 | dispatch({ type: 'error', error }); 70 | }; 71 | 72 | const setValue = (value?: T) => { 73 | dispatch({ type: 'value', value }); 74 | }; 75 | 76 | return useMemo( 77 | () => ({ 78 | error: state.error, 79 | loading: state.loading, 80 | reset, 81 | setError, 82 | setValue, 83 | value: state.value, 84 | }), 85 | [state.error, state.loading, reset, setError, setValue, state.value] 86 | ); 87 | }; 88 | --------------------------------------------------------------------------------