Ich versuche herauszufinden, wie ein Firebase-Listener verwendet wird, damit Cloud-Firestore-Daten mit React Hooks-Updates aktualisiert werden.
Anfangs habe ich dies mit einer Klassenkomponente mit einer componentDidMount-Funktion gemacht, um die Firestore-Daten abzurufen.
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,
});
}
Das bricht ab, wenn die Seite aktualisiert wird, also versuche ich herauszufinden, wie ich den Listener bewegen kann, um auf Hooks zu reagieren.
Ich habe das React-Firebase-Hooks- Tool installiert - obwohl ich nicht herausfinden kann, wie ich die Anweisungen lesen soll, damit es funktioniert.
Ich habe eine Funktionskomponente wie folgt:
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);
Diese Komponente wird wie folgt in einen authUser-Wrapper auf Routenebene eingeschlossen:
<Route path={ROUTES.DASHBOARD2} render={props => (
<AuthUserContext.Consumer>
{ authUser => (
<Dashboard2 authUser={authUser} {...props} />
)}
</AuthUserContext.Consumer>
)} />
Ich habe eine firebase.js-Datei, die wie folgt in den Firestore eingesteckt wird:
class Firebase {
constructor() {
app.initializeApp(config).firestore();
/* helpers */
this.fieldValue = app.firestore.FieldValue;
/* Firebase APIs */
this.auth = app.auth();
this.db = app.firestore();
}
Außerdem wird ein Listener definiert, der weiß, wann sich der AuthUser ändert:
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;
}
});
};
Dann habe ich in meinem Firebase-Kontext-Setup:
import FirebaseContext, { withFirebase } from './Context';
import Firebase from '../../firebase';
export default Firebase;
export { FirebaseContext, withFirebase };
Der Kontext wird in einem withFirebase-Wrapper wie folgt eingerichtet:
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;
Dann habe ich in meinem withAuthentication-HOC einen Kontextanbieter wie folgt:
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;
Derzeit - wenn ich dies versuche, erhalte ich eine Fehlermeldung in der Dashboard2-Komponente, die besagt:
Firebase 'ist nicht definiert
Ich habe Firebase in Kleinbuchstaben ausprobiert und den gleichen Fehler erhalten.
Ich habe auch firebase.firestore und Firebase.firestore ausprobiert. Ich bekomme den gleichen Fehler.
Ich frage mich, ob ich mein HOC nicht mit einer Funktionskomponente verwenden kann.
Ich habe diese Demo-App und diesen Blog-Beitrag gesehen .
Den Ratschlägen im Blog folgend, habe ich eine neue firebase / contextReader.jsx erstellt mit:
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
}
Dann versuche ich, meine App.jsx in diesen Reader zu packen mit:
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} />
Wenn ich das versuche, erhalte ich eine Fehlermeldung:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth ist keine Funktion
Ich habe diesen Beitrag gesehen, der sich mit diesem Fehler befasst, und habe versucht, Garn zu deinstallieren und neu zu installieren. Es macht keinen Unterschied.
Wenn ich mir die Demo-App anschaue , wird vorgeschlagen, dass der Kontext mithilfe einer 'Interface'-Methode erstellt werden sollte. Ich kann nicht sehen, woher das kommt - ich kann keine Referenz finden, um es in der Dokumentation zu erklären.
Ich kann die Anweisungen nur verstehen, wenn ich versuche, das zu tun, was ich getan habe, um dies anzuschließen.
Ich habe diesen Beitrag gesehen, der versucht, Firestore anzuhören, ohne React-Firebase-Hooks zu verwenden. Die Antworten verweisen auf den Versuch, herauszufinden, wie dieses Tool verwendet wird.
Ich habe diese ausgezeichnete Erklärung gelesen, die beschreibt, wie man sich von HOCs zu Haken bewegt. Ich bin nicht sicher, wie ich den Firebase-Listener integrieren soll.
Ich habe diesen Beitrag gesehen, der ein hilfreiches Beispiel dafür liefert, wie man darüber nachdenkt. Ich bin mir nicht sicher, ob ich dies in der authListener-KomponenteDidMount versuchen soll - oder in der Dashboard-Komponente, die versucht, es zu verwenden.
NÄCHSTER VERSUCH Ich habe diesen Beitrag gefunden , der versucht, das gleiche Problem zu lösen.
Wenn ich versuche, die von Shubham Khatri angebotene Lösung zu implementieren, richte ich die Firebase-Konfiguration wie folgt ein:
Ein Kontextanbieter mit: import React, {useContext} from 'react'; Firebase aus '../../firebase' importieren;
const FirebaseContext = React.createContext();
export const FirebaseProvider = (props) => (
<FirebaseContext.Provider value={new Firebase()}>
{props.children}
</FirebaseContext.Provider>
);
Der Kontext-Hook hat dann:
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;
Dann verpacke ich in der index.js die App im Provider wie folgt:
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();
Wenn ich dann versuche, den Listener in der Komponente zu verwenden, die ich habe:
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;
Und ich versuche es als Route ohne Komponenten oder Auth-Wrapper zu verwenden:
<Route path={ROUTES.DASHBOARD2} component={Dashboard2} />
Wenn ich das versuche, erhalte ich eine Fehlermeldung:
Importversuch: 'FirebaseContext' wird nicht aus '../Firebase/ContextHookProvider' exportiert.
Diese Fehlermeldung ist sinnvoll, da ContextHookProvider FirebaseContext nicht exportiert - es exportiert FirebaseProvider -, aber wenn ich nicht versuche, dies in Dashboard2 zu importieren, kann ich in der Funktion, die versucht, es zu verwenden, nicht darauf zugreifen.
Ein Nebeneffekt dieses Versuchs ist, dass meine Anmeldemethode nicht mehr funktioniert. Es wird jetzt eine Fehlermeldung generiert, die besagt:
TypeError: Die Eigenschaft 'doCreateUserWithEmailAndPassword' von null kann nicht gelesen werden
Ich werde dieses Problem später lösen - aber es muss eine Möglichkeit geben, herauszufinden, wie man mit Firebase reagiert, ohne Monate dieser Schleife durch Millionen von Wegen zu führen, die nicht funktionieren, um ein grundlegendes Authentifizierungssetup zu erhalten. Gibt es ein Starter-Kit für Firebase (Firestore), das mit Reaktionshaken funktioniert?
Nächster Versuch Ich habe versucht, dem Ansatz in diesem udemy-Kurs zu folgen - aber es funktioniert nur, um eine Formulareingabe zu generieren. Es gibt keinen Listener, der die Routen umgibt , um sie an den authentifizierten Benutzer anzupassen.
Ich habe versucht, dem Ansatz in diesem Youtube-Tutorial zu folgen - mit dem dieses Repo funktioniert. Es zeigt, wie Hooks verwendet werden, nicht jedoch, wie der Kontext verwendet wird.
NÄCHSTER VERSUCH Ich habe dieses Repo gefunden , das einen gut durchdachten Ansatz für die Verwendung von Hooks mit Firestore zu haben scheint. Ich kann den Code jedoch nicht verstehen.
Ich habe dies geklont und versucht, alle öffentlichen Dateien hinzuzufügen. Wenn ich es dann ausführe, kann ich den Code nicht zum Laufen bringen. Ich bin mir nicht sicher, was in den Anweisungen fehlt, um dies zum Laufen zu bringen, um festzustellen, ob der Code Lektionen enthält, die zur Lösung dieses Problems beitragen können.
NÄCHSTER VERSUCH
Ich habe die Divjoy-Vorlage gekauft, die als Setup für Firebase beworben wird (sie ist nicht für Firestore eingerichtet, falls jemand anderes dies als Option in Betracht zieht).
Diese Vorlage schlägt einen Auth-Wrapper vor, der die Konfiguration der App initialisiert - jedoch nur für die Auth-Methoden - und daher neu strukturiert werden muss, damit ein anderer Kontextanbieter für den Firestore zugelassen wird. Wenn Sie diesen Prozess durcheinander bringen und den in diesem Beitrag gezeigten Prozess verwenden, bleibt ein Fehler im folgenden Rückruf übrig:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Es weiß nicht, was Feuerbasis ist. Dies liegt daran, dass es im Firebase-Kontextanbieter definiert ist, der importiert und definiert wird (in der Funktion useProvideAuth ()) als:
const firebase = useContext(FirebaseContext)
Ohne Chancen auf den Rückruf lautet der Fehler:
React Hook useEffect hat eine fehlende Abhängigkeit: 'firebase'. Schließen Sie es entweder ein oder entfernen Sie das Abhängigkeitsarray
Wenn ich versuche, diese Konstante zum Rückruf hinzuzufügen, wird eine Fehlermeldung angezeigt, die besagt:
React Hook "useContext" kann nicht innerhalb eines Rückrufs aufgerufen werden. React Hooks müssen in einer React-Funktionskomponente oder einer benutzerdefinierten React Hook-Funktion aufgerufen werden
NÄCHSTER VERSUCH
Ich habe meine Firebase-Konfigurationsdatei auf nur Konfigurationsvariablen reduziert (ich schreibe Helfer in die Kontextanbieter für jeden Kontext, den ich verwenden möchte).
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;
Ich habe dann einen Auth-Kontextanbieter wie folgt:
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];
};
Ich habe auch einen Firebase-Kontextanbieter wie folgt erstellt:
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>
)
}
Dann verpacke ich in index.js die App in den Firebase-Anbieter
ReactDom.render(
<FirebaseProvider>
<App />
</FirebaseProvider>,
document.getElementById("root"));
serviceWorker.unregister();
und in meiner Routenliste habe ich die relevanten Routen in den Authentifizierungsanbieter eingeschlossen:
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;
Bei diesem speziellen Versuch kehre ich zu dem Problem zurück, das zuvor mit diesem Fehler gekennzeichnet wurde:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth ist keine Funktion
Es verweist auf diese Zeile des Authentifizierungsanbieters, die das Problem verursacht:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Ich habe versucht, in Firebase großgeschriebenes F zu verwenden, und es wird der gleiche Fehler generiert.
Wenn ich Tristans Rat versuche, entferne ich all diese Dinge und versuche, meine Abmeldemethode als Nichtauflistungsmethode zu definieren (ich weiß nicht, warum er nicht die Firebase-Sprache verwendet - aber wenn sein Ansatz funktioniert, würde ich mich mehr anstrengen um herauszufinden warum). Wenn ich versuche, seine Lösung zu verwenden, lautet die Fehlermeldung:
TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ default (...) ist keine Funktion
Die Antwort auf diesen Beitrag schlägt vor, () nach der Authentifizierung zu entfernen. Wenn ich das versuche, erhalte ich eine Fehlermeldung:
TypeError: Die Eigenschaft 'onAuthStateChanged' von undefined kann nicht gelesen werden
Dieser Beitrag weist jedoch auf ein Problem mit der Art und Weise hin, wie Firebase in die Auth-Datei importiert wird.
Ich habe es importiert als: importiere Firebase von "../firebase";
Firebase ist der Name der Klasse.
Die von Tristan empfohlenen Videos sind hilfreiche Hintergrundinformationen, aber ich bin derzeit in Episode 9 und habe immer noch nicht den Teil gefunden, der zur Lösung dieses Problems beitragen soll. Weiß jemand, wo man das findet?
NÄCHSTER VERSUCH Als nächstes - und nur um das Kontextproblem zu lösen - habe ich sowohl createContext als auch useContext importiert und versucht, sie wie in dieser Dokumentation gezeigt zu verwenden.
Ich kann keinen Fehler erhalten, der besagt:
Fehler: Ungültiger Hook-Aufruf. Hooks können nur innerhalb des Körpers einer Funktionskomponente aufgerufen werden. Dies kann aus einem der folgenden Gründe geschehen: ...
Ich habe die Vorschläge in diesem Link durchgesehen, um dieses Problem zu lösen, und kann es nicht herausfinden. Ich habe keine der in dieser Anleitung zur Fehlerbehebung aufgeführten Probleme.
Derzeit sieht die Kontextanweisung wie folgt aus:
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>
);
Ich habe Zeit damit verbracht, diesen udemy-Kurs zu verwenden, um zu versuchen, den Kontext und das Hook- Element für dieses Problem herauszufinden. Nach dem Betrachten ist der einzige Aspekt der von Tristan unten vorgeschlagenen Lösung, dass die createContext-Methode in seinem Beitrag nicht korrekt aufgerufen wird. Es muss "React.createContext" sein, aber es kommt der Lösung des Problems immer noch nicht nahe.
Ich stecke immer noch fest.
Kann jemand sehen, was hier schief gelaufen ist?
export
zu export const FirebaseContext
?