web-dev-qa-db-ja.com

さまざまなコンポーネントのfirebase authとCloud Firestoreを単一のfirebaseアプリとして使用する方法

ReactプロジェクトでFirebaseを使用して、認証とデータベース機能を提供しようとしています。

私の_App.js_には

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

_<Component />_によってレンダリングされた_App.js_と呼ばれる他のコンポーネントでは、これを使用してデータベースを初期化します

_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);を入れようとしましたが、2回インスタンス化したことを示す新しいエラーが再度表示されました。

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

したがって、私が思いついた1つの回避策は、_App.js_でapp.initializeApp(firebaseConfig);の直後にコンテキストを作成し、const db = app.firestore();でデータベースを作成して、その値をコンテキストに渡して、消費する_<Component />_。しかし、これが良い解決策であるかどうかはわかりません。

私の質問は FirebaseアプリがすでにAndroidで初期化されているかどうかを確認する方法 と1つの理由で異なります。その質問のために、2つ目のFirebaseアプリに接続しようとはしていません。プロジェクト全体でFirebaseアプリが1つだけあり、認証とデータベースの2つのサービスを提供します。

その質問の解決策を_<Component />_で使用してみました

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

const db = app.firestore();
_

しかし、機能しませんでしたが、それでもUncaught FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).エラーが発生します

9
Joji

表示されているエラーメッセージは有効であり、モジュールのインポート順序に関係しています。 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.jsdbをインポートします。

この答えは、問題が何であったかを理解することについての詳細です...そして、私があなたのContextProviderが実際に本当によく、一般的に実践されていると私が思う解決策に関する限りです。

Es6モジュールの詳細 ここ

Firebase Reactセットアップ

お役に立てば幸いです。

1

アプリとコンポーネントでFirebaseの異なるインスタンスを使用します。

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

それでは、firebaseApp.jsからfirebaseをインポートして使用できます。詳細 こちら

4
Valerii

Firebase構成用のfirebaseConfig.jsディレクトリにsrc/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

プロジェクトのルートフォルダー(srcの親)の.envファイルにAPIキーを保存します。

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>
2
Srividya K

言ったとおりのコンテキストを使用するか、またはredux(ミドルウェアを使用して初期化し、グローバル状態を使用してdbを保持する)を使用できます。

// 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を使用して実装する場合、ミドルウェア、アクション、レデューサーですべてのfirebaseを分離できます(ここでアイデアとサンプルを取得できます https://github.com/Canner/redux-firebase -ミドルウェア );また、コンポーネントにビジネスロジックを保持して、データコレクションの格納方法と管理方法をコンポーネントが知る必要がないようにします。コンポーネントは状態とアクションについてのみ知っている必要があります。

2

Firebaseをreactで使用するために見つけた最良の方法は、最初に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;

your-component.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);
      })
  )
);
0

ログインしたユーザーデータを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 のようなストア管理ライブラリを使用することをお勧めします。この例では、すべてを1つのコンポーネントで仕上げています。しかし、リアルタイムでは、ストア管理ライブラリを使用するこのようなユースケースでは、複雑なコンポーネントアーキテクチャが役立つ場合があります。