J'essaie d'utiliser Firebase dans mon projet React pour fournir les fonctionnalités d'authentification et de base de données.
Dans mon App.js
J'ai
import app from "firebase/app";
import "firebase/auth";
app.initializeApp(firebaseConfig);
Dans mes autres composants appelés <Component />
Rendus par App.js
J'ai ceci pour initialiser la base de données
import app from "firebase/app";
import "firebase/firestore";
const db = app.firestore();
Mais cette fois j'ai eu cette erreur
Uncaught FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app).
J'ai donc essayé de mettre app.initializeApp(firebaseConfig);
dans ce composant aussi mais j'ai de nouveau une erreur pour me dire que j'ai instancié deux fois.
Uncaught FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).
Donc, une solution de contournement que j'ai trouvée est de créer un contexte à App.js
Et juste après app.initializeApp(firebaseConfig);
J'ai créé la base de données par const db = app.firestore();
et passer la valeur au contexte et laisser le <Component />
à consommer. Cependant, je ne sais pas si c'est une bonne solution ou non.
Ma question est différente de Comment vérifier si une application Firebase est déjà initialisée sur Android pour une raison. Je n'essaye pas de me connecter à une deuxième application Firebase comme c'était le cas pour cette question. Il n'y a qu'une seule application Firebase pour l'ensemble de mon projet, pour fournir deux services: l'authentification et la base de données.
J'ai essayé la solution de cette question à utiliser dans <Component />
if (!app.apps.length) {
app.initializeApp(firebaseConfig);
}
const db = app.firestore();
mais ça n'a pas marché ça me donne quand même Uncaught FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).
erreur
Le message d'erreur que vous recevez est valide et concerne l'ordre dans lequel vos modules sont importés. Les modules ES6 sont pré-analysés afin de résoudre d'autres importations avant l'exécution du code.
En supposant que tout en haut de votre App.js
ressemble à ceci:
import Component from '../component';
...
import app from "firebase/app";
import "firebase/auth";
app.initializeApp(firebaseConfig);
Le problème ici est qu'à l'intérieur de import Component from '.../component';
import app from "firebase/app";
import "firebase/firestore";
const db = app.firestore();
Ce code est exécuté avant vous:
app.initializeApp(firebaseConfig);
Il existe de nombreuses façons de résoudre ce problème, y compris certaines solutions présentées ci-dessus et la proposition de simplement stocker votre configuration Firebase dans un firebase-config.js
et importez db
à partir de cela.
Cette réponse consiste davantage à comprendre quel était le problème ... et en ce qui concerne la solution, je pense que votre Context
Provider
est en fait vraiment bon et couramment utilisé.
En savoir plus sur les modules es6 ici
J'espère que cela pourra aider.
Vous utilisez différentes instances de Firebase dans l'application et le composant.
// firebaseApp.js
import firebase from 'firebase'
const config = {
apiKey: "...",
authDomain: "...",
databaseURL: "....",
projectId: "...",
messagingSenderId: "..."
};
firebase.initializeApp(config);
export default firebase;
Vous pouvez alors importer Firebase à partir de firebaseApp.js et l'utiliser. Plus de détails ici
Créer un fichier firebaseConfig.js
dans src/firebase
répertoire pour la configuration de la base de feu:
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 };
Tout ce que vous avez à faire dans Component.js
est:
import { db } from './firebase/firebaseConfig.js'; // Assuming Component.js is in the src folder
Stockez les clés API dans un .env
fichier dans le dossier racine du projet (le parent de src
):
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>
Vous pouvez utiliser un contexte comme vous l'avez dit ou redux (en utilisant un middleware pour initialiser, et un état global pour conserver la base de données):
// 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>
)
Ensuite, vous pouvez utiliser withFirebase dans vos composants de conteneur:
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)
Vous pouvez voir le code entier ici: https://github.com/the-road-to-react-with-firebase/react-firestore-authentication et un tutoriel ici: https : //www.robinwieruch.de/complete-firebase-authentication-react-tutorial/
Si vous l'implémentez à l'aide de redux et de redux-thunk, vous pouvez isoler tous les éléments Firebase dans le middleware, les actions et les réducteurs (vous pouvez prendre des idées et des échantillons ici: https://github.com/Canner/redux-firebase -middleware ); et conservez la logique métier dans vos composants afin qu'ils n'aient pas besoin de savoir comment vos collections de données sont stockées et gérées. Les composants ne doivent connaître que les états et les actions.
La meilleure façon d'utiliser Firebase dans React est d'abord d'initialiser et d'exporter Firebase pour ensuite exécuter les fonctions souhaitées.
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);
})
}
...
Mais je recommanderais d'utiliser 'redux-thunk' pour stocker des données sur l'état:
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);
})
)
);
Voici un exemple simple de stockage des données d'utilisateur connecté de google OAuth dans la collection firestore.
Stockez la configuration de Firebase dans un fichier séparé
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;
Je vous suggère d'utiliser des bibliothèques de gestion de magasin comme Redux lorsque vous souhaitez partager l'état entre les composants. Dans cet exemple, nous avons terminé tout en un seul composant. Mais en temps réel, vous pouvez avoir une architecture de composant complexe dans un tel cas d'utilisation, l'utilisation de bibliothèques de gestion de magasin peut être utile.