Sto cercando di capire come utilizzare un listener Firebase in modo che i dati del cloud store vengano aggiornati con gli aggiornamenti degli hook di reazione.
Inizialmente, l'ho realizzato utilizzando un componente di classe con una funzione componentDidMount per ottenere i dati del deposito.
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,
});
}
Ciò si interrompe quando la pagina si aggiorna, quindi sto cercando di capire come spostare l'ascoltatore per reagire agli hook.
Ho installato i reattivi firebase-hook strumento - anche se non riesco a capire come leggere le istruzioni per riuscire a farlo funzionare.
Ho un componente di funzione come segue:
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);
Questo componente è racchiuso in un wrapper authUser a livello di route come segue:
<Route path={ROUTES.DASHBOARD2} render={props => (
<AuthUserContext.Consumer>
{ authUser => (
<Dashboard2 authUser={authUser} {...props} />
)}
</AuthUserContext.Consumer>
)} />
Ho un file firebase.js, che si collega al fuoco come segue:
class Firebase {
constructor() {
app.initializeApp(config).firestore();
/* helpers */
this.fieldValue = app.firestore.FieldValue;
/* Firebase APIs */
this.auth = app.auth();
this.db = app.firestore();
}
Definisce inoltre un ascoltatore per sapere quando cambia l'autenticatore:
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;
}
});
};
Quindi, nella mia configurazione del contesto Firebase ho:
import FirebaseContext, { withFirebase } from './Context';
import Firebase from '../../firebase';
export default Firebase;
export { FirebaseContext, withFirebase };
Il contesto è configurato in un wrapper withFirebase come segue:
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;
Quindi, nel mio HOC withAuthentication, ho un provider di contesto come:
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;
Attualmente - quando provo questo, ricevo un errore nel componente Dashboard2 che dice:
Firebase 'non è definito
Ho provato la base di fuoco in minuscolo e ho ottenuto lo stesso errore.
Ho anche provato firebase.firestore e Firebase.firestore. Ho fatto lo stesso errore.
Mi chiedo se non riesco a utilizzare il mio HOC con un componente di funzione?
Ho visto questa app demo e questo post sul blog .
Seguendo i consigli nel blog, ho realizzato un nuovo firebase / contextReader.jsx con:
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
}
Quindi provo ad avvolgere il mio App.jsx in quel lettore con:
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} />
Quando provo questo, ricevo un errore che dice:
TipoErrore: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth non è una funzione
Ho visto questo post affrontare quell'errore e ho provato a disinstallare e reinstallare filato. Non fa differenza.
Quando guardo l' app demo , mi viene in mente che il contesto dovrebbe essere creato usando un metodo "interfaccia". Non riesco a vedere da dove provenga, non riesco a trovare un riferimento per spiegarlo nella documentazione.
Non riesco a dare un senso alle istruzioni oltre a provare quello che ho fatto per collegarlo.
Ho visto questo post che tenta di ascoltare il camino senza usare i ganci di reazione-fuoco. Le risposte rimandano al tentativo di capire come usare questo strumento.
Ho letto questa eccellente spiegazione che spiega come spostarsi dai HOC ai ganci. Sono bloccato con come integrare l'ascoltatore base di fuoco.
Ho visto questo post che fornisce un utile esempio su come pensare di farlo. Non sono sicuro se dovrei provare a farlo nel componente authListenerDidMount - o nel componente Dashboard che sta cercando di usarlo.
PROSSIMO TENTATIVO Ho trovato questo post , che sta cercando di risolvere lo stesso problema.
Quando provo a implementare la soluzione offerta da Shubham Khatri, ho impostato la configurazione della base di fuoco come segue:
Un provider di contesto con: import React, {useContext} da 'reagire'; importare Firebase da '../../firebase';
const FirebaseContext = React.createContext();
export const FirebaseProvider = (props) => (
<FirebaseContext.Provider value={new Firebase()}>
{props.children}
</FirebaseContext.Provider>
);
L'hook di contesto ha quindi:
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;
Quindi in index.js avvolgo l'app nel provider come:
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();
Quindi, quando provo ad usare l'ascoltatore nel componente ho:
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;
E provo ad usarlo come percorso senza componenti o wrapper di autenticazione:
<Route path={ROUTES.DASHBOARD2} component={Dashboard2} />
Quando provo questo, ricevo un errore che dice:
Tentativo di errore di importazione: "FirebaseContext" non viene esportato da "../Firebase/ContextHookProvider".
Questo messaggio di errore ha senso, poiché ContextHookProvider non esporta FirebaseContext - esporta FirebaseProvider - ma se non provo a importarlo in Dashboard2 - non posso accedervi nella funzione che tenta di utilizzarlo.
Un effetto collaterale di questo tentativo è che il mio metodo di iscrizione non funziona più. Ora genera un messaggio di errore che dice:
TypeError: impossibile leggere la proprietà 'doCreateUserWithEmailAndPassword' di null
Risolverò questo problema in seguito, ma ci deve essere un modo per capire come usare la reazione con firebase che non implichi mesi di questo ciclo attraverso milioni di strade che non funzionano per ottenere un'installazione di base di autorizzazione. Esiste uno starter kit per firebase (fireestore) che funziona con ganci reattivi?
Il prossimo tentativo ho provato a seguire l'approccio in questo corso udemy - ma funziona solo per generare un input di modulo - non c'è un ascoltatore che metta in giro le rotte per adattarsi all'utente autenticato.
Ho provato a seguire l'approccio in questo tutorial di YouTube - che ha questo repository su cui lavorare. Mostra come usare gli hook, ma non come usare il contesto.
PROSSIMO TENTATIVO Ho trovato questo repository che sembra avere un approccio ben ponderato all'utilizzo dei ganci con il camino. Tuttavia, non riesco a dare un senso al codice.
Ho clonato questo - e ho provato ad aggiungere tutti i file pubblici e poi quando lo eseguo - non riesco davvero a far funzionare il codice. Non sono sicuro di cosa manchi dalle istruzioni su come farlo funzionare per vedere se ci sono lezioni nel codice che possono aiutare a risolvere questo problema.
PROSSIMO TENTATIVO
Ho acquistato il modello divjoy, che è pubblicizzato come impostato per firebase (non è impostato per il negozio di alimentari nel caso in cui qualcun altro lo stia considerando come un'opzione).
Tale modello propone un wrapper di autenticazione che inizializza la configurazione dell'app - ma solo per i metodi di autenticazione - quindi deve essere ristrutturato per consentire a un altro provider di contesto per il deposito. Quando confondi quel processo e utilizzi il processo mostrato in questo post , ciò che rimane è un errore nel seguente callback:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Non sa cos'è la base di fuoco. Questo perché è definito nel provider di contesto Firebase che viene importato e definito (nella funzione useProvideAuth ()) come:
const firebase = useContext(FirebaseContext)
Senza possibilità di richiamata, l'errore dice:
React Hook useEffect ha una dipendenza mancante: 'firebase'. Includerlo o rimuovere l'array di dipendenze
Oppure, se provo ad aggiungere quella const al callback, ricevo un errore che dice:
React Hook "useContext" non può essere chiamato all'interno di un callback. I React Hook devono essere chiamati in un componente della funzione React o in una funzione React Hook personalizzata
PROSSIMO TENTATIVO
Ho ridotto il mio file di configurazione firebase solo a variabili di configurazione (scriverò helper nei provider di contesto per ogni contesto che voglio usare).
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;
Ho quindi un provider di contesto di autenticazione come segue:
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];
};
Ho anche creato un provider di contesto Firebase come segue:
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>
)
}
Quindi, in index.js avvolgo l'app nel provider di Firebase
ReactDom.render(
<FirebaseProvider>
<App />
</FirebaseProvider>,
document.getElementById("root"));
serviceWorker.unregister();
e nel mio elenco di rotte, ho spostato le rotte pertinenti nel provider di autenticazione:
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;
In questo particolare tentativo, sono tornato al problema segnalato in precedenza con questo errore:
TipoErrore: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth non è una funzione
Indica questa riga del provider di autenticazione come la creazione del problema:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Ho provato a usare la maiuscola F in Firebase e genera lo stesso errore.
Quando provo il consiglio di Tristan, rimuovo tutte quelle cose e provo a definire il mio metodo di annullamento dell'iscrizione come metodo non ascoltato (non so perché non stia usando il linguaggio firebase - ma se il suo approccio funzionasse, proverei di più per capire perché). Quando provo ad usare la sua soluzione, il messaggio di errore dice:
TipoErrore: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ default (...) non è una funzione
La risposta a questo post suggerisce di rimuovere () da after auth. Quando provo quello, ottengo un errore che dice:
TypeError: impossibile leggere la proprietà 'onAuthStateChanged' di undefined
Tuttavia, questo post suggerisce un problema con il modo in cui firebase viene importato nel file auth.
L'ho importato come: import Firebase da "../firebase";
Firebase è il nome della classe.
I video consigliati da Tristan sono utili, ma al momento sono sull'episodio 9 e non ho ancora trovato la parte che dovrebbe aiutare a risolvere questo problema. Qualcuno sa dove trovarlo?
PROSSIMO TENTATIVO Avanti - e cercando di risolvere solo il problema di contesto - Ho importato sia createContext che useContext e ho provato a usarli come mostrato in questa documentazione.
Non riesco a farmi passare un errore che dice:
Errore: chiamata hook non valida. Gli hook possono essere chiamati solo all'interno del corpo di un componente di funzione. Questo potrebbe accadere per uno dei seguenti motivi: ...
Ho seguito i suggerimenti in questo link per provare a risolvere questo problema e non riesco a capirlo. Non ho riscontrato nessuno dei problemi indicati in questa guida alla risoluzione dei problemi.
Attualmente - la dichiarazione di contesto ha il seguente aspetto:
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>
);
Ho trascorso del tempo usando questo corso udemy per cercare di capire il contesto e agganciare l'elemento a questo problema - dopo averlo visto - l'unico aspetto della soluzione proposta da Tristan di seguito è che il metodo createContext non è chiamato correttamente nel suo post. deve essere "React.createContext" ma non si avvicina ancora per risolvere il problema.
Sono ancora bloccato.
Qualcuno può vedere cosa è andato storto qui?
export
a export const FirebaseContext
?