【问题标题】:How to use firebase auth and Cloud Firestore from different components as a single firebase App如何将来自不同组件的 firebase auth 和 Cloud Firestore 用作单个 Firebase 应用程序
【发布时间】:2019-12-05 16:16:01
【问题描述】:

我正在尝试在我的 React 项目中使用 firebase 来提供身份验证和数据库功能。

在我的App.js 我有

import app from "firebase/app";
import "firebase/auth";
app.initializeApp(firebaseConfig);

在由App.js 渲染的名为<Component /> 的其他组件中,我有这个来初始化数据库

import app from "firebase/app";
import "firebase/firestore";
const db = app.firestore();

但是这次我得到了这个错误

Uncaught FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app).

所以我也尝试将app.initializeApp(firebaseConfig); 放入此组件中,但我再次收到一个新错误,告诉我我实例化了两次。

Uncaught FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).

所以我想出的一种解决方法是在App.jsapp.initializeApp(firebaseConfig); 之后创建一个上下文,我通过const db = app.firestore(); 创建了数据库并将值传递给上下文并让<Component /> 使用。但是我不知道这是否是一个好的解决方案。

我的问题与How to check if a Firebase App is already initialized on Android 不同,原因之一。我没有尝试连接到第二个 Firebase 应用程序,因为它是针对那个问题的。我整个项目只有一个 Firebase App,提供两种服务:auth 和 database。

我尝试在<Component />中使用该问题的解决方案

if (!app.apps.length) {
  app.initializeApp(firebaseConfig);
}

const db = app.firestore();

但它没有工作它仍然给我Uncaught FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).错误

【问题讨论】:

  • 能否请您添加有关在您的项目中使用 Firestore 的更多详细信息?我还在使用类似的用例,例如用于身份验证的 firebase 和用于存储的 firestore。

标签: javascript reactjs firebase google-cloud-firestore


【解决方案1】:

您在应用和组件中使用不同的 Firebase 实例。

// firebaseApp.js
import firebase from 'firebase'
const config = {
    apiKey: "...",
    authDomain: "...",
    databaseURL: "....",
    projectId: "...",
    messagingSenderId: "..."
};
firebase.initializeApp(config);
export default firebase;

您可以从 firebaseApp.js 导入 firebase 并使用它。更多详情here

【讨论】:

    【解决方案2】:

    src/firebase 目录中创建一个文件firebaseConfig.js 用于firebase 配置:

    import firebase from 'firebase/app'; // doing import firebase from 'firebase' or import * as firebase from firebase is not good practice. 
    import 'firebase/auth';
    import 'firebase/firestore';
    
    // Initialize Firebase
    let config = {
        apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
        authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
        databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL,
        projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
        storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
        messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
    };
    firebase.initializeApp(config);
    
    const auth = firebase.auth();
    const db = firebase.firestore();
    
    const googleAuthProvider = new firebase.auth.GoogleAuthProvider();
    const emailAuthProvider = new firebase.auth.EmailAuthProvider();
    
    export { auth, firebase, db, googleAuthProvider, emailAuthProvider };
    

    Component.js 中你所要做的就是:

    import { db } from './firebase/firebaseConfig.js'; // Assuming Component.js is in the src folder
    

    将api密钥存储在项目根文件夹(src的父级)的.env文件中:

    REACT_APP_FIREBASE_API_KEY=<api-key>
    REACT_APP_FIREBASE_AUTH_DOMAIN=<auth-domain>
    REACT_APP_FIREBASE_DATABASE_URL=<db-url>
    REACT_APP_FIREBASE_PROJECT_ID=<proj-name>
    REACT_APP_FIREBASE_STORAGE_BUCKET=<storage-bucket>
    REACT_APP_FIREBASE_MESSAGING_SENDER_ID=<message-sender-id>
    

    【讨论】:

      【解决方案3】:

      您收到的错误消息是有效的,并且与您的模块的导入顺序有关。 预先解析 ES6 模块,以便在执行代码之前解析进一步的导入。

      假设您的App.js 的最顶部看起来像这样:

      import Component from '../component';
      ...
      
      import app from "firebase/app";
      import "firebase/auth";
      app.initializeApp(firebaseConfig);
      

      这里的问题在于import Component from '.../component';里面

      import app from "firebase/app";
      import "firebase/firestore";
      const db = app.firestore();
      

      该代码在您执行之前被执行:

      app.initializeApp(firebaseConfig);
      

      有很多方法可以解决这个问题,包括上面介绍的一些解决方案以及将您的 firebase 配置存储在 firebase-config.js 并导入 db 的建议 从此。

      这个答案更多地是关于了解问题所在......就解决方案而言,我认为您的Context Provider 实际上非常好并且很常用。

      更多关于 es6 模块here

      Firebase React Setup

      希望对您有所帮助。

      【讨论】:

        【解决方案4】:

        您可以使用您所说的上下文或redux(使用中间件进行初始化,并使用全局状态来保留数据库):

        // Main (for example index.js)
        <FirebaseContext.Provider value={new Firebase()}>
            <App />
        </FirebaseContext.Provider>
        

        Firebase.js:

        import app from 'firebase/app'
        import 'firebase/firestore'
        
        const config = {
          apiKey: process.env.API_KEY,
          databaseURL: process.env.DATABASE_URL,
          projectId: process.env.PROJECT_ID,
          storageBucket: process.env.STORAGE_BUCKET
        }
        
        export default class Firebase {
          constructor() {
            app.initializeApp(config)
        
            // Firebase APIs
            this._db = app.firestore()
          }
        
          // DB data API
          data = () => this._db.collection('yourdata')
        
          ...
        }
        

        FirebaseContext.js:

        import React from 'react'
        
        const FirebaseContext = React.createContext(null)
        
        export const withFirebase = Component => props => (
          <FirebaseContext.Consumer>
            {firebase => <Component {...props} firebase={firebase} />}
          </FirebaseContext.Consumer>
        )
        

        然后您可以在容器组件中使用 withFirebase:

        class YourContainerComponent extends React.PureComponent {
          state = {
            data: null,
            loading: false
          }
        
          componentDidMount() {
            this._onListenForMessages()
          }
        
          _onListenForMessages = () => {
            this.setState({ loading: true }, () => {
              this.unsubscribe = this.props.firebase
                .data()
                .limit(10)
                .onSnapshot(snapshot => {
                  if (snapshot.size) {
                    let data = []
                    snapshot.forEach(doc =>
                      data.push({ ...doc.data(), uid: doc.id })
                    )
                    this.setState({
                      data,
                      loading: false
                    })
                  } else {
                    this.setState({ data: null, loading: false })
                  }
                })
             })
           })
          }
        
          componentWillUnmount() {
            if (this._unsubscribe) {
              this._unsubscribe()
            }
          }
        }
        
        
        export default withFirebase(YourContainerComponent)
        

        您可以在此处查看完整代码:https://github.com/the-road-to-react-with-firebase/react-firestore-authentication 和此处的教程:https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial/

        如果你使用 redux 和 redux-thunk 来实现它,你可以隔离中间件、动作和 reducer 中的所有 firebase 东西(你可以在这里获取想法和示例:https://github.com/Canner/redux-firebase-middleware);并将业务逻辑保留在您的组件中,这样他们就不需要知道您的数据集合是如何存储和管理的。组件应该只知道状态和动作。

        【讨论】:

        • 您好,感谢您的及时回复。我看到你把firebase-config 文件放在.env 中。它被认为比将其存储在 js 文件 firebase-config.js 中并从中导入更好的做法?
        • 还有,当组件需要db 时,为什么使用Redux 管理数据库实例(即db)比只使用import {db} from './db' 更好?
        • 嗨。最好将您的凭据放在您不会上传到存储库的文件中(git、mercurial 等)。将其保存在环境文件中可以让您拥有不同的配置,例如,一个用于生产,另一个用于测试,另一个用于开发。您可以使用安全凭证管理器为要实施解决方案的服务器创建容器并提供凭证。
        • Redux 或任何你可以用来保持关注点分离 (SoC) 的东西。将容器中的组件(负责将数据和操作绑定到展示组件的组件)和展示组件分开是一种很好的做法。因此,您的逻辑是关于获取和存储数据,这些操作独立于持久性技术(db、localstorage 等),您可以将所有这些逻辑保存在一个地方,所以如果您明天可以更改 Firebase 以进行 API 调用(或其他技术),您可以在一个地方更换逻辑,您无需触摸组件。
        • 您好,感谢您的及时回复。是否有一些教程可以让我学习如何在存储firebase-config 的情况下编写我的.env?现在我只是在需要时导入我的firebase-config.js,正如你所说的那样不安全。我还发现,当我部署我的 firebase 项目时,由于我使用 Web SDK 而不设置自己的服务器,人们仍然可以通过使用开发工具检查 Sources 选项卡来查看我的 firebase-config 中的内容。将其保存在环境文件中是否有助于防止这种情况发生?
        【解决方案5】:

        我发现在 react 中使用 firebase 的最佳方法是首先初始化并导出 firebase 以执行所需的功能。

        helper-firebase.js

        import * as firebase from 'firebase/app';
        import 'firebase/auth';
        import 'firebase/firestore';
        
        // Everyone can read client side javascript, no need to use an .env file
        // I only used environment variables for firebase-admin
        import { FIREBASE_CONFIG } from '../../config';
        
        // Initialize Firebase
        firebase.initializeApp(FIREBASE_CONFIG);
        export const auth = firebase.auth();
        export const provider = new firebase.auth.GoogleAuthProvider();
        export const db = firebase.firestore();
        export default firebase;
        

        你的组件.js

        import {
          auth,
          provider,
          db,
        } from '../../../helpers/helper-firebase';
        
        ...
        componentDidMount() {
          this.usersRef = db.collection('users');
          // Look for user changes
          auth.onAuthStateChanged(this.authChanged);
        }
        
        authChanged(user) {
          // Set value on the database
          this.usersRef.doc(user.uid).set({
            lastLogin: new Date(),
          }, { merge: true })
            .then(() => {
               console.log('User Updated');
            })
            .catch((error) => {
               console.error(error.message);
            });
        }
        
        login() {
          auth.signInWithPopup(provider)
            .then((res) => {
              console.log(newUser);
            })
            .catch((error) => {
              console.error(error.message);
            })
        }
        ...
        

        但我建议使用“redux-thunk”来存储状态数据:

        redux-actions.js

        import {
          auth,
        } from '../../../helpers/helper-firebase';
        
        export const setUser = payload => ({
          type: AUTH_CHANGED,
          payload,
        });
        
        export const onAuthChange = () => (
          dispatch => auth.onAuthStateChanged((user) => {
            // console.log(user);
            if (user) {
              dispatch(setUser(user));
            } else {
              dispatch(setUser());
            }
          })
        );
        
        export const authLogout = () => (
          dispatch => (
            auth.signOut()
              .then(() => {
                dispatch(setUser());
              })
              .catch((error) => {
                console.error(error.message);
              })
          )
        );
        

        【讨论】:

          【解决方案6】:

          以下是将登录用户数据从 google OAuth 存储到 firestore 集合中的简单示例。

          将 firebase 配置存储在单独的文件中

          firebase.utils.js

          import firebase from 'firebase/app';
          import 'firebase/firestore';
          import 'firebase/auth';
          
          //replace your config here
          const config = {
            apiKey: '*****',
            authDomain: '******',
            databaseURL: '******',
            projectId: '******,
            storageBucket: '********',
            messagingSenderId: '*******',
            appId: '**********'
          };
          
          firebase.initializeApp(config);
          
          export const createUserProfileDocument = async (userAuth) => {
            if (!userAuth) return;
          
            const userRef = firestore.doc(`users/${userAuth.uid}`);
          
            const snapShot = await userRef.get();
          
            if (!snapShot.exists) {
              const { displayName, email } = userAuth;
              const createdAt = new Date();
              try {
                await userRef.set({
                  displayName,
                  email,
                  createdAt
                });
              } catch (error) {
                console.log('error creating user', error.message);
              }
            }
          
            return userRef;
          };
          
          export const auth = firebase.auth();
          export const firestore = firebase.firestore();
          
          const provider = new firebase.auth.GoogleAuthProvider();
          provider.setCustomParameters({ prompt: 'select_account' });
          export const signInWithGoogle = () => auth.signInWithPopup(provider);
          

          App.js

           import React from 'react';
           import { auth, createUserProfileDocument, signInWithGoogle } from './firebase.utils';
          
           class App extends React.Component {
            constructor() {
              super();
          
              this.state = {
                currentUser: null
              };
          
             }
          
            unsubscribeFromAuth = null;
          
            componentDidMount() {
              this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
                if (userAuth) {
                  const userRef = await createUserProfileDocument(userAuth);
          
                  userRef.onSnapshot(snapShot => {
                    this.setState({
                      currentUser: {
                        id: snapShot.id,
                        ...snapShot.data()
                      }
                    });
          
                    console.log(this.state);
                  });
                }
          
                this.setState({ currentUser: userAuth });
              });
            }
          
            componentWillUnmount() {
              this.unsubscribeFromAuth();
            }
          
            render() {
             return(
               <React.Fragment>
                 { this.state.currentUser ? 
                    (<Button onClick={() => auth.signOut()}>Sign Out</Button>) 
                   : 
                   (<Button onClick={signInWithGoogle} > Sign in with Google </Button>) 
                 }
          
               </React.Fragment>
              )
             }
          }
          
          export default App;
          

          当你想在组件之间共享状态时,我建议你使用像 Redux 这样的存储管理库。在此示例中,我们在单个组件中完成了所有操作。但在实时情况下,您可能有一个复杂的组件架构,在这种用例中使用商店管理库可能会派上用场。

          【讨论】:

            猜你喜欢
            • 2021-04-02
            • 2018-05-20
            • 1970-01-01
            • 2020-07-05
            • 1970-01-01
            • 2021-12-25
            • 2021-08-27
            • 2023-02-07
            • 2020-08-15
            相关资源
            最近更新 更多