Estoy tratando de descubrir cómo usar un escucha de Firebase para que los datos del almacén de incendios en la nube se actualicen con actualizaciones de ganchos de reacción.
Inicialmente, hice esto usando un componente de clase con una función componentDidMount para obtener los datos del almacén de incendios.
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,
});
}
Eso se rompe cuando la página se actualiza, así que estoy tratando de descubrir cómo mover al oyente para que reaccione.
He instalado la herramienta react-firebase-hooks , aunque no puedo entender cómo leer las instrucciones para que funcione.
Tengo un componente de función de la siguiente manera:
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);
Este componente está envuelto en un contenedor authUser en el nivel de ruta de la siguiente manera:
<Route path={ROUTES.DASHBOARD2} render={props => (
<AuthUserContext.Consumer>
{ authUser => (
<Dashboard2 authUser={authUser} {...props} />
)}
</AuthUserContext.Consumer>
)} />
Tengo un archivo firebase.js, que se conecta a firestore de la siguiente manera:
class Firebase {
constructor() {
app.initializeApp(config).firestore();
/* helpers */
this.fieldValue = app.firestore.FieldValue;
/* Firebase APIs */
this.auth = app.auth();
this.db = app.firestore();
}
También define un oyente para saber cuándo cambia el authUser:
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;
}
});
};
Luego, en mi configuración de contexto de Firebase tengo:
import FirebaseContext, { withFirebase } from './Context';
import Firebase from '../../firebase';
export default Firebase;
export { FirebaseContext, withFirebase };
El contexto se configura en un contenedor withFirebase de la siguiente manera:
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;
Luego, en mi withAuthentication HOC, tengo un proveedor de contexto como:
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;
Actualmente, cuando intento esto, aparece un error en el componente Dashboard2 que dice:
Firebase 'no está definido
Intenté Firebase en minúsculas y obtuve el mismo error.
También probé firebase.firestore y Firebase.firestore. Me sale el mismo error.
Me pregunto si no puedo usar mi HOC con un componente de función.
He visto esta aplicación de demostración y esta publicación de blog .
Siguiendo los consejos del blog, hice un nuevo 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
}
Luego trato de envolver mi App.jsx en ese lector 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} />
Cuando intento esto, aparece un error que dice:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth no es una función
He visto esta publicación que trata sobre ese error y he intentado desinstalar y reinstalar hilo. No hace ninguna diferencia.
Cuando miro la aplicación de demostración , sugiere que el contexto debe crearse utilizando un método de 'interfaz'. No puedo ver de dónde viene esto: no puedo encontrar una referencia para explicarlo en la documentación.
No puedo entender las instrucciones más que intentar lo que he hecho para conectar esto.
He visto esta publicación que intenta escuchar firestore sin usar react-firebase-hooks. Las respuestas apuntan a tratar de descubrir cómo usar esta herramienta.
He leído esta excelente explicación que cómo pasar de los HOC a los ganchos. Estoy atrapado en cómo integrar el oyente firebase.
He visto esta publicación que proporciona un ejemplo útil de cómo pensar en hacer esto. No estoy seguro de si debería intentar hacer esto en el componente authListenerDidMount, o en el componente Panel que está tratando de usarlo.
PRÓXIMO INTENTO Encontré esta publicación , que está tratando de resolver el mismo problema.
Cuando trato de implementar la solución ofrecida por Shubham Khatri, configuro la configuración de Firebase de la siguiente manera:
Un proveedor de contexto con: import React, {useContext} de 'react'; importar Firebase desde '../../firebase';
const FirebaseContext = React.createContext();
export const FirebaseProvider = (props) => (
<FirebaseContext.Provider value={new Firebase()}>
{props.children}
</FirebaseContext.Provider>
);
El gancho de contexto tiene entonces:
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;
Luego, en index.js, envuelvo la aplicación en el proveedor como:
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();
Luego, cuando trato de usar el oyente en el componente que tengo:
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;
Y trato de usarlo como una ruta sin componentes o envoltorio de autenticación:
<Route path={ROUTES.DASHBOARD2} component={Dashboard2} />
Cuando intento esto, aparece un error que dice:
Intento de error de importación: 'FirebaseContext' no se exporta desde '../Firebase/ContextHookProvider'.
Ese mensaje de error tiene sentido, porque ContextHookProvider no exporta FirebaseContext, exporta FirebaseProvider, pero si no intento importar esto en Dashboard2, entonces no puedo acceder a él en la función que intenta usarlo.
Un efecto secundario de este intento es que mi método de registro ya no funciona. Ahora genera un mensaje de error que dice:
TypeError: no se puede leer la propiedad 'doCreateUserWithEmailAndPassword' de null
Resolveré este problema más tarde, pero debe haber una manera de descubrir cómo usar Reaccionar con Firebase que no implique meses de este ciclo a través de millones de vías que no funcionan para obtener una configuración de autenticación básica. ¿Existe un kit de inicio para firebase (firestore) que funcione con ganchos de reacción?
En el siguiente intento , intenté seguir el enfoque en este curso udemy , pero solo funciona para generar una entrada de formulario, no hay un oyente para colocar las rutas para ajustar con el usuario autenticado.
Traté de seguir el enfoque en este tutorial de YouTube , que tiene este repositorio para trabajar. Muestra cómo usar ganchos, pero no cómo usar el contexto.
PRÓXIMO INTENTO Encontré este repositorio que parece tener un enfoque bien pensado para usar ganchos con Firestore. Sin embargo, no puedo entender el código.
Cloné esto, e intenté agregar todos los archivos públicos y luego, cuando lo ejecuté, en realidad no puedo hacer que el código funcione. No estoy seguro de lo que falta en las instrucciones sobre cómo hacer que esto se ejecute para ver si hay lecciones en el código que puedan ayudar a resolver este problema.
PRÓXIMO INTENTO
Compré la plantilla divjoy, que se anuncia como configurada para firebase (no está configurada para firefire en caso de que alguien más esté considerando esto como una opción).
Esa plantilla propone un contenedor de autenticación que inicializa la configuración de la aplicación, pero solo para los métodos de autenticación, por lo que debe reestructurarse para permitir otro proveedor de contexto para el almacén de incendios. Cuando sales de ese proceso y usas el proceso que se muestra en esta publicación , lo que queda es un error en la siguiente devolución de llamada:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
No sabe qué es firebase. Esto se debe a que está definido en el proveedor de contexto de Firebase que se importa y define (en la función useProvideAuth ()) como:
const firebase = useContext(FirebaseContext)
Sin posibilidades de devolución de llamada, el error dice:
React Hook useEffect tiene una dependencia que falta: 'firebase'. Inclúyalo o elimine la matriz de dependencias
O, si intento agregar esa constante a la devolución de llamada, aparece un error que dice:
No se puede llamar a React Hook "useContext" dentro de una devolución de llamada. Los React Hooks deben llamarse en un componente de función React o en una función personalizada React Hook
PRÓXIMO INTENTO
He reducido mi archivo de configuración de Firebase a solo variables de configuración (escribiré ayudantes en los proveedores de contexto para cada contexto que quiera usar).
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;
Luego tengo un proveedor de contexto de autenticación de la siguiente manera:
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];
};
También hice un proveedor de contexto de Firebase de la siguiente manera:
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>
)
}
Luego, en index.js envuelvo la aplicación en el proveedor de Firebase
ReactDom.render(
<FirebaseProvider>
<App />
</FirebaseProvider>,
document.getElementById("root"));
serviceWorker.unregister();
y en mi lista de rutas, he incluido las rutas relevantes en el proveedor de autenticación:
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;
En este intento particular, vuelvo al problema marcado anteriormente con este error:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth no es una función
Señala que esta línea del proveedor de autenticación crea el problema:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Intenté usar F mayúscula en Firebase y genera el mismo error.
Cuando pruebo el consejo de Tristan, elimino todas esas cosas e intento definir mi método de cancelación de suscripción como un método no listado (no sé por qué no está usando el lenguaje firebase, pero si su enfoque funcionara, me esforzaría más) para averiguar por qué). Cuando trato de usar su solución, el mensaje de error dice:
TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ default (...) no es una función
La respuesta a esta publicación sugiere eliminar () de después de la autenticación. Cuando intento eso, aparece un error que dice:
TypeError: no se puede leer la propiedad 'onAuthStateChanged' de undefined
Sin embargo, esta publicación sugiere un problema con la forma en que se importa firebase en el archivo de autenticación.
Lo tengo importado como: importar Firebase desde "../firebase";
Firebase es el nombre de la clase.
Los videos recomendados por Tristan son antecedentes útiles, pero actualmente estoy en el episodio 9 y todavía no encuentro la parte que se supone que ayuda a resolver este problema. ¿Alguien sabe dónde encontrar eso?
SIGUIENTE INTENTO A continuación, y tratando de resolver solo el problema de contexto, importé createContext y useContext e intenté usarlos como se muestra en esta documentación.
No puedo pasar un error que dice:
Error: llamada de enlace no válida. Los ganchos solo se pueden llamar dentro del cuerpo de un componente de función. Esto podría suceder por una de las siguientes razones: ...
He leído las sugerencias en este enlace. para tratar de resolver este problema y no puedo resolverlo. No tengo ninguno de los problemas que se muestran en esta guía de solución de problemas.
Actualmente, la declaración de contexto tiene el siguiente aspecto:
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>
);
Pasé tiempo usando este curso udemy para tratar de descubrir el contexto y el elemento de enganche a este problema, después de verlo, el único aspecto de la solución propuesta por Tristan a continuación es que el método createContext no se llama correctamente en su publicación. necesita ser "React.createContext" pero aún no se acerca a resolver el problema.
Todavía estoy atascado.
¿Alguien puede ver lo que salió mal aquí?
export
a export const FirebaseContext
?