J'essaie de comprendre comment utiliser un écouteur Firebase afin que les données du cloud firestore soient actualisées avec les mises à jour de hooks de réaction.
Initialement, j'ai fait cela en utilisant un composant de classe avec une fonction componentDidMount pour obtenir les données du firestore.
this.props.firebase.db
.collection('users')
// .doc(this.props.firebase.db.collection('users').doc(this.props.firebase.authUser.uid))
.doc(this.props.firebase.db.collection('users').doc(this.props.authUser.uid))
.get()
.then(doc => {
this.setState({ name: doc.data().name });
// loading: false,
});
}
Cela se casse lorsque la page est mise à jour, j'essaie donc de comprendre comment déplacer l'auditeur pour réagir aux hooks.
J'ai installé l' outil react-firebase-hooks - bien que je n'arrive pas à comprendre comment lire les instructions pour pouvoir le faire fonctionner.
J'ai un composant de fonction comme suit:
import React, { useState, useEffect } from 'react';
import { useDocument } from 'react-firebase-hooks/firestore';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
useRouteMatch,
} from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import { compose } from 'recompose';
import { withFirebase } from '../Firebase/Index';
import { AuthUserContext, withAuthorization, withEmailVerification, withAuthentication } from '../Session/Index';
function Dashboard2(authUser) {
const FirestoreDocument = () => {
const [value, loading, error] = useDocument(
Firebase.db.doc(authUser.uid),
//firebase.db.doc(authUser.uid),
//firebase.firestore.doc(authUser.uid),
{
snapshotListenOptions: { includeMetadataChanges: true },
}
);
return (
<div>
<p>
{error && <strong>Error: {JSON.stringify(error)}</strong>}
{loading && <span>Document: Loading...</span>}
{value && <span>Document: {JSON.stringify(value.data())}</span>}
</p>
</div>
);
}
}
export default withAuthentication(Dashboard2);
Ce composant est encapsulé dans un wrapper authUser au niveau de l'itinéraire comme suit:
<Route path={ROUTES.DASHBOARD2} render={props => (
<AuthUserContext.Consumer>
{ authUser => (
<Dashboard2 authUser={authUser} {...props} />
)}
</AuthUserContext.Consumer>
)} />
J'ai un fichier firebase.js, qui se branche sur firestore comme suit:
class Firebase {
constructor() {
app.initializeApp(config).firestore();
/* helpers */
this.fieldValue = app.firestore.FieldValue;
/* Firebase APIs */
this.auth = app.auth();
this.db = app.firestore();
}
Il définit également un écouteur pour savoir quand l'authUser change:
onAuthUserListener(next, fallback) {
// onUserDataListener(next, fallback) {
return this.auth.onAuthStateChanged(authUser => {
if (authUser) {
this.user(authUser.uid)
.get()
.then(snapshot => {
let snapshotData = snapshot.data();
let userData = {
...snapshotData, // snapshotData first so it doesn't override information from authUser object
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerifed,
providerData: authUser.providerData
};
setTimeout(() => next(userData), 0); // escapes this Promise's error handler
})
.catch(err => {
// TODO: Handle error?
console.error('An error occured -> ', err.code ? err.code + ': ' + err.message : (err.message || err));
setTimeout(fallback, 0); // escapes this Promise's error handler
});
};
if (!authUser) {
// user not logged in, call fallback handler
fallback();
return;
}
});
};
Ensuite, dans ma configuration de contexte Firebase, j'ai:
import FirebaseContext, { withFirebase } from './Context';
import Firebase from '../../firebase';
export default Firebase;
export { FirebaseContext, withFirebase };
Le contexte est configuré dans un wrapper withFirebase comme suit:
import React from 'react';
const FirebaseContext = React.createContext(null);
export const withFirebase = Component => props => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
export default FirebaseContext;
Ensuite, dans mon withAuthentication HOC, j'ai un fournisseur de contexte comme:
import React from 'react';
import { AuthUserContext } from '../Session/Index';
import { withFirebase } from '../Firebase/Index';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: null,
};
}
componentDidMount() {
this.listener = this.props.firebase.auth.onAuthStateChanged(
authUser => {
authUser
? this.setState({ authUser })
: this.setState({ authUser: null });
},
);
}
componentWillUnmount() {
this.listener();
};
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withFirebase(WithAuthentication);
};
export default withAuthentication;
Actuellement - quand j'essaye ceci, j'obtiens une erreur dans le composant Dashboard2 qui dit:
Firebase 'n'est pas défini
J'ai essayé la base de feu en minuscules et j'obtiens la même erreur.
J'ai également essayé firebase.firestore et Firebase.firestore. J'ai la même erreur.
Je me demande si je ne peux pas utiliser mon HOC avec un composant de fonction?
J'ai vu cette application de démonstration et cet article de blog .
En suivant les conseils du blog, j'ai créé un nouveau firebase / contextReader.jsx avec:
import React, { useEffect, useContext } from 'react';
import Firebase from '../../firebase';
export const userContext = React.createContext({
user: null,
})
export const useSession = () => {
const { user } = useContext(userContext)
return user
}
export const useAuth = () => {
const [state, setState] = React.useState(() =>
{ const user = firebase.auth().currentUser
return { initializing: !user, user, }
}
);
function onChange(user) {
setState({ initializing: false, user })
}
React.useEffect(() => {
// listen for auth state changes
const unsubscribe = firebase.auth().onAuthStateChanged(onChange)
// unsubscribe to the listener when unmounting
return () => unsubscribe()
}, [])
return state
}
Ensuite, j'essaie d'envelopper mon App.jsx dans ce lecteur avec:
function App() {
const { initializing, user } = useAuth()
if (initializing) {
return <div>Loading</div>
}
// )
// }
// const App = () => (
return (
<userContext.Provider value={{ user }}>
<Router>
<Navigation />
<Route path={ROUTES.LANDING} exact component={StandardLanding} />
Lorsque j'essaye ceci, j'obtiens une erreur qui dit:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth n'est pas une fonction
J'ai vu ce post traiter de cette erreur et j'ai essayé de désinstaller et réinstaller le fil. Ça ne fait aucune différence.
Lorsque je regarde l' application de démonstration , cela suggère que le contexte devrait être créé à l'aide d'une méthode «d'interface». Je ne vois pas d'où cela vient - je ne trouve pas de référence pour l'expliquer dans la documentation.
Je ne peux pas comprendre les instructions à part essayer ce que j'ai fait pour le brancher.
J'ai vu ce post qui tente d'écouter Firestore sans utiliser de hooks de base de réaction. Les réponses renvoient à essayer de comprendre comment utiliser cet outil.
J'ai lu cette excellente explication qui explique comment passer des HOC aux crochets. Je suis coincé avec la façon d'intégrer l'écouteur Firebase.
J'ai vu cet article qui fournit un exemple utile pour savoir comment procéder. Je ne sais pas si je devrais essayer de le faire dans le composant authListenerDidMount - ou dans le composant Dashboard qui essaie de l'utiliser.
PROCHAINE TENTATIVE J'ai trouvé ce message , qui tente de résoudre le même problème.
Lorsque j'essaie d'implémenter la solution proposée par Shubham Khatri, j'ai configuré la configuration Firebase comme suit:
Un fournisseur de contexte avec: import React, {useContext} de 'react'; importer Firebase depuis '../../firebase';
const FirebaseContext = React.createContext();
export const FirebaseProvider = (props) => (
<FirebaseContext.Provider value={new Firebase()}>
{props.children}
</FirebaseContext.Provider>
);
Le hook de contexte a alors:
import React, { useEffect, useContext, useState } from 'react';
const useFirebaseAuthentication = (firebase) => {
const [authUser, setAuthUser] = useState(null);
useEffect(() =>{
const unlisten =
firebase.auth.onAuthStateChanged(
authUser => {
authUser
? setAuthUser(authUser)
: setAuthUser(null);
},
);
return () => {
unlisten();
}
});
return authUser
}
export default useFirebaseAuthentication;
Ensuite, dans l'index.js, j'encapsule l'application dans le fournisseur en tant que:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App/Index';
import {FirebaseProvider} from './components/Firebase/ContextHookProvider';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<FirebaseProvider>
<App />
</FirebaseProvider>,
document.getElementById('root')
);
serviceWorker.unregister();
Ensuite, lorsque j'essaie d'utiliser l'écouteur dans le composant, j'ai:
import React, {useContext} from 'react';
import { FirebaseContext } from '../Firebase/ContextHookProvider';
import useFirebaseAuthentication from '../Firebase/ContextHook';
const Dashboard2 = (props) => {
const firebase = useContext(FirebaseContext);
const authUser =
useFirebaseAuthentication(firebase);
return (
<div>authUser.email</div>
)
}
export default Dashboard2;
Et j'essaie de l'utiliser comme une route sans composants ni wrapper d'authentification:
<Route path={ROUTES.DASHBOARD2} component={Dashboard2} />
Lorsque j'essaye ceci, j'obtiens une erreur qui dit:
Tentative d’importation: «FirebaseContext» n’est pas exporté depuis «../Firebase/ContextHookProvider».
Ce message d'erreur est logique, car ContextHookProvider n'exporte pas FirebaseContext - il exporte FirebaseProvider - mais si je n'essaie pas d'importer cela dans Dashboard2 - alors je ne peux pas y accéder dans la fonction qui essaie de l'utiliser.
Un effet secondaire de cette tentative est que ma méthode d'inscription ne fonctionne plus. Il génère maintenant un message d'erreur qui dit:
TypeError: Impossible de lire la propriété «doCreateUserWithEmailAndPassword» de null
Je résoudrai ce problème plus tard, mais il doit y avoir un moyen de comprendre comment utiliser React avec FireBase qui n'implique pas des mois de cette boucle à travers des millions de voies qui ne fonctionnent pas pour obtenir une configuration d'authentification de base. Existe-t-il un kit de démarrage pour Firebase (Firestore) qui fonctionne avec les crochets React?
Prochaine tentative, j'ai essayé de suivre l'approche de ce cours udemy - mais cela ne fonctionne que pour générer une entrée de formulaire - il n'y a pas d'écouteur pour contourner les routes à ajuster avec l'utilisateur authentifié.
J'ai essayé de suivre l'approche de ce tutoriel YouTube - sur lequel ce repo peut fonctionner. Il montre comment utiliser les hooks, mais pas comment utiliser le contexte.
PROCHAINE TENTATIVE J'ai trouvé ce repo qui semble avoir une approche bien pensée pour utiliser des crochets avec un coupe-feu. Cependant, je ne peux pas comprendre le code.
J'ai cloné cela - et j'ai essayé d'ajouter tous les fichiers publics, puis quand je l'ai exécuté - je ne peux pas réellement faire fonctionner le code. Je ne suis pas sûr de ce qui manque dans les instructions pour savoir comment exécuter cela afin de voir s'il y a des leçons dans le code qui peuvent aider à résoudre ce problème.
PROCHAINE TENTATIVE
J'ai acheté le modèle divjoy, qui est annoncé comme étant configuré pour Firebase (il n'est pas configuré pour Firestore au cas où quelqu'un d'autre envisage cela comme une option).
Ce modèle propose un wrapper d'authentification qui initialise la configuration de l'application - mais uniquement pour les méthodes d'authentification - il doit donc être restructuré pour autoriser un autre fournisseur de contexte pour Firestore. Lorsque vous vous trompez à travers ce processus et utilisez le processus montré dans ce post , ce qui reste est une erreur dans le rappel suivant:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Il ne sait pas ce qu'est la base de feu. En effet, il est défini dans le fournisseur de contexte Firebase qui est importé et défini (dans la fonction useProvideAuth ()) comme:
const firebase = useContext(FirebaseContext)
Sans chances de rappel, l'erreur indique:
React Hook useEffect a une dépendance manquante: 'firebase'. Soit l'inclure ou supprimer le tableau de dépendances
Ou, si j'essaye d'ajouter cette constante au rappel, j'obtiens une erreur qui dit:
React Hook "useContext" ne peut pas être appelé dans un rappel. React Hooks doit être appelé dans un composant de fonction React ou une fonction React Hook personnalisée
PROCHAINE TENTATIVE
J'ai réduit mon fichier de configuration Firebase à seulement des variables de configuration (j'écrirai des assistants dans les fournisseurs de contexte pour chaque contexte que je veux utiliser).
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
const devConfig = {
apiKey: process.env.REACT_APP_DEV_API_KEY,
authDomain: process.env.REACT_APP_DEV_AUTH_DOMAIN,
databaseURL: process.env.REACT_APP_DEV_DATABASE_URL,
projectId: process.env.REACT_APP_DEV_PROJECT_ID,
storageBucket: process.env.REACT_APP_DEV_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_DEV_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_DEV_APP_ID
};
const prodConfig = {
apiKey: process.env.REACT_APP_PROD_API_KEY,
authDomain: process.env.REACT_APP_PROD_AUTH_DOMAIN,
databaseURL: process.env.REACT_APP_PROD_DATABASE_URL,
projectId: process.env.REACT_APP_PROD_PROJECT_ID,
storageBucket: process.env.REACT_APP_PROD_STORAGE_BUCKET,
messagingSenderId:
process.env.REACT_APP_PROD_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_PROD_APP_ID
};
const config =
process.env.NODE_ENV === 'production' ? prodConfig : devConfig;
class Firebase {
constructor() {
firebase.initializeApp(config);
this.firebase = firebase;
this.firestore = firebase.firestore();
this.auth = firebase.auth();
}
};
export default Firebase;
J'ai ensuite un fournisseur de contexte d'authentification comme suit:
import React, { useState, useEffect, useContext, createContext } from "react";
import Firebase from "../firebase";
const authContext = createContext();
// Provider component that wraps app and makes auth object ...
// ... available to any child component that calls useAuth().
export function ProvideAuth({ children }) {
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}
// Hook for child components to get the auth object ...
// ... and update when it changes.
export const useAuth = () => {
return useContext(authContext);
};
// Provider hook that creates auth object and handles state
function useProvideAuth() {
const [user, setUser] = useState(null);
const signup = (email, password) => {
return Firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(response => {
setUser(response.user);
return response.user;
});
};
const signin = (email, password) => {
return Firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(response => {
setUser(response.user);
return response.user;
});
};
const signout = () => {
return Firebase
.auth()
.signOut()
.then(() => {
setUser(false);
});
};
const sendPasswordResetEmail = email => {
return Firebase
.auth()
.sendPasswordResetEmail(email)
.then(() => {
return true;
});
};
const confirmPasswordReset = (password, code) => {
// Get code from query string object
const resetCode = code || getFromQueryString("oobCode");
return Firebase
.auth()
.confirmPasswordReset(resetCode, password)
.then(() => {
return true;
});
};
// Subscribe to user on mount
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
// Subscription unsubscribe function
return () => unsubscribe();
}, []);
return {
user,
signup,
signin,
signout,
sendPasswordResetEmail,
confirmPasswordReset
};
}
const getFromQueryString = key => {
return queryString.parse(window.location.search)[key];
};
J'ai également créé un fournisseur de contexte Firebase comme suit:
import React, { createContext } from 'react';
import Firebase from "../../firebase";
const FirebaseContext = createContext(null)
export { FirebaseContext }
export default ({ children }) => {
return (
<FirebaseContext.Provider value={ Firebase }>
{ children }
</FirebaseContext.Provider>
)
}
Ensuite, dans index.js, j'encapsule l'application dans le fournisseur firebase
ReactDom.render(
<FirebaseProvider>
<App />
</FirebaseProvider>,
document.getElementById("root"));
serviceWorker.unregister();
et dans ma liste de routes, j'ai encapsulé les routes pertinentes dans le fournisseur d'authentification:
import React from "react";
import IndexPage from "./index";
import { Switch, Route, Router } from "./../util/router.js";
import { ProvideAuth } from "./../util/auth.js";
function App(props) {
return (
<ProvideAuth>
<Router>
<Switch>
<Route exact path="/" component={IndexPage} />
<Route
component={({ location }) => {
return (
<div
style={{
padding: "50px",
width: "100%",
textAlign: "center"
}}
>
The page <code>{location.pathname}</code> could not be found.
</div>
);
}}
/>
</Switch>
</Router>
</ProvideAuth>
);
}
export default App;
Sur cette tentative particulière, je reviens au problème signalé plus tôt avec cette erreur:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth n'est pas une fonction
Il indique que cette ligne du fournisseur d'authentification crée le problème:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
J'ai essayé d'utiliser F majuscule dans Firebase et cela génère la même erreur.
Lorsque j'essaye le conseil de Tristan, je supprime toutes ces choses et essaie de définir ma méthode de désabonnement comme une méthode non écoutée (je ne sais pas pourquoi il n'utilise pas le langage firebase - mais si son approche fonctionnait, j'essaierais plus fort pour comprendre pourquoi). Lorsque j'essaie d'utiliser sa solution, le message d'erreur indique:
TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ par défaut (...) n'est pas une fonction
La réponse à ce post suggère de supprimer () après l'authentification. Lorsque j'essaye, j'obtiens une erreur qui dit:
TypeError: Impossible de lire la propriété «onAuthStateChanged» de non défini
Cependant, ce message suggère un problème avec la façon dont Firebase est importé dans le fichier d'authentification.
Je l'ai importé en tant que: importez Firebase de "../firebase";
Firebase est le nom de la classe.
Les vidéos recommandées par Tristan sont utiles, mais je suis actuellement dans l'épisode 9 et je ne trouve toujours pas la partie censée aider à résoudre ce problème. Est-ce que quelqu'un sait où trouver ça?
PROCHAINE TENTATIVE Ensuite - et en essayant de résoudre le problème de contexte uniquement - j'ai importé à la fois createContext et useContext et j'ai essayé de les utiliser comme indiqué dans cette documentation.
Je ne peux pas passer une erreur qui dit:
Erreur: appel de raccordement non valide. Les crochets ne peuvent être appelés qu'à l'intérieur du corps d'un composant de fonction. Cela peut se produire pour l'une des raisons suivantes: ...
J'ai parcouru les suggestions de ce lien pour essayer de résoudre ce problème et je ne peux pas le comprendre. Je n'ai aucun des problèmes indiqués dans ce guide de dépannage.
Actuellement, l'énoncé de contexte se présente comme suit:
import React, { useContext } from 'react';
import Firebase from "../../firebase";
export const FirebaseContext = React.createContext();
export const useFirebase = useContext(FirebaseContext);
export const FirebaseProvider = props => (
<FirebaseContext.Provider value={new Firebase()}>
{props.children}
</FirebaseContext.Provider>
);
J'ai passé du temps à utiliser ce cours udemy pour essayer de comprendre le contexte et les éléments liés à ce problème - après l'avoir vu - le seul aspect de la solution proposée par Tristan ci-dessous est que la méthode createContext n'est pas appelée correctement dans son article. il doit s'agir de "React.createContext" mais il ne parvient toujours pas à résoudre le problème.
Je suis toujours coincé.
Quelqu'un peut-il voir ce qui ne va pas ici?
export
à export const FirebaseContext
?