Estoy tratando de descubrir cómo obtener un nombre de usuario que es un atributo almacenado en una colección de usuarios, que se ha fusionado con los atributos creados por el modelo de autenticación de Firebase.
Puedo acceder a authUser, lo que me da los campos limitados que firebase recopila en la herramienta de autenticación, y luego intento acceder desde allí a la colección de usuarios relacionada (que usa el mismo uid).
Tengo un consumidor de contexto de reacción con:
import React from 'react';
const AuthUserContext = React.createContext(null);
export default AuthUserContext;
Luego, en mi componente, estoy tratando de usar:
const Test = () => (
<AuthUserContext.Consumer>
{authUser => (
<div>
{authUser.email} // I can access the attributes in the authentication collection
{authUser.uid.user.name} //i cannot find a way to get the details in the related user collection document - where the uid on the collection is the same as the uid on the authentication table
</div>
)}
</AuthUserContext.Consumer>
);
const condition = authUser => !!authUser;
export default compose(
withEmailVerification,
withAuthorization(condition),
)(Test);
En mi firebase.js, creo que he intentado fusionar los atributos authUser del modelo de autenticación con los atributos de la colección de usuarios 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();
onAuthUserListener = (next, fallback) =>
this.auth.onAuthStateChanged(authUser => {
if (authUser) {
this.user(authUser.uid)
.get()
.then(snapshot => {
const dbUser = snapshot.data();
// default empty roles
if (!dbUser.roles) {
dbUser.roles = {};
}
// merge auth and db user
authUser = {
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerified,
providerData: authUser.providerData,
...dbUser,
};
next(authUser);
});
} else {
fallback();
}
});
No puedo encontrar una manera de llegar del usuario de autenticación (que funciona para llevarme a los atributos de autenticación), a la colección de usuarios que tiene una identificación con el mismo uid de la colección de autenticación.
He visto esta publicación , que parece tener el mismo problema e intenté averiguar cuál es la respuesta que se sugiere, pero parece que no puedo encontrar una manera de pasar de la colección de autenticación a la colección de usuarios. y no sé qué me está haciendo la fusión si no me da acceso a los atributos de la colección de usuarios de authUser.
Traté de usar un ayudante en mi firebase.js para darme un usuario de un uid, pero eso tampoco parece ayudar.
user = uid => this.db.doc(`users/${uid}`);
users = () => this.db.collection('users');
Siguiente intento
Para agregar más antecedentes, he creado un componente de prueba que puede registrar (pero no representar) authUser, de la siguiente manera:
import React, { Component } from 'react';
import { withFirebase } from '../Firebase/Index';
import { Button, Layout } from 'antd';
import { AuthUserContext, withAuthorization, withEmailVerification } from '../Session/Index';
class Test extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
user: null,
...props.location.state,
};
}
componentDidMount() {
if (this.state.user) {
return;
}
this.setState({ loading: true });
// this.unsubscribe = this.props.firebase
// .user(authUser.uid)
// .onSnapshot(snapshot => {
// const userData = snapshot.data();
// console.log(userData);
// this.setState({
// user: snapshot.data(),
// loading: false,
// });
// });
}
componentWillUnmount() {
this.unsubscribe && this.unsubscribe();
}
render() {
const { user, loading } = this.state;
return (
<div>
<AuthUserContext.Consumer>
{authUser => (
console.log(authUser),
<p>
</p>
)}
</AuthUserContext.Consumer>
</div>
);
};
}
export default Test;
El registro muestra los detalles de uid, correo electrónico, etc. en los registros, pero se encuentra entre una larga lista de elementos, muchos de los cuales están precedidos por 1 o 2 letras (no puedo encontrar una clave para averiguar cuál es cada uno de estos prefijos letras significan). Ejemplo extraído a continuación:
ACTUALIZACIÓN DE ESTE COMENTARIO:
Anteriormente, dije: los campos para uid, correo electrónico, etc. no parecen estar anidados debajo de estos prefijos, pero si trato de:
console.log(authUser.email)
, Recibo un error que dice:
TypeError: no se puede leer la propiedad 'correo electrónico' de nulo
La actualización: me acabo de dar cuenta de que en el registro de la consola, tengo que expandir un menú desplegable que está etiquetado:
Q {I: Matriz (0), l:
para ver el atributo de correo electrónico. ¿Alguien sabe a qué alude esta charlatanería? No puedo encontrar una clave para descubrir qué significa Q, I o l, para saber si se supone que debo hacer referencia a estas cosas para obtener los atributos relevantes en la tabla de Autenticación. Tal vez si puedo resolver eso, puedo encontrar una manera de llegar a la colección de usuarios usando el uid de la colección de autenticación.
¿Alguien ha usado reaccionar en el front-end, con un consumidor de contexto para averiguar quién es el usuario actual? Si es así, ¿cómo accede a sus atributos en el modelo de Autenticación y cómo accede a los atributos en la colección de Usuarios relacionada (donde el docId en el documento de Usuario es el uid de la tabla de Autenticación)?
PRÓXIMO INTENTO
El siguiente intento ha producido un resultado muy extraño.
Tengo 2 páginas separadas que son consumidores contextuales. La diferencia entre ellos es que uno es una función y el otro es un componente de clase.
En el componente de función, puedo renderizar {authUser.email}. Cuando trato de hacer lo mismo en el componente de clase, aparece un error que dice:
TypeError: no se puede leer la propiedad 'correo electrónico' de nulo
Este error proviene de la misma sesión con el mismo usuario conectado.
Nota: aunque la documentación de Firebase dice que una propiedad currentUser está disponible en autenticación, no he podido hacer que funcione en absoluto.
Mi componente de función tiene:
import React from 'react';
import { Link } from 'react-router-dom';
import { compose } from 'recompose';
import { AuthUserContext, withAuthorization, withEmailVerification } from '../Session/Index';
const Account = () => (
<AuthUserContext.Consumer>
{authUser => (
<div>
{authUser.email}
</div>
)}
</AuthUserContext.Consumer>
);
// const condition = authUser => !!authUser;
// export default compose(
// withEmailVerification,
// withAuthorization(condition),
// )(Account);
export default Account;
Si bien no puedo acceder a los atributos de la colección de usuarios donde el docId en el documento del usuario es el mismo que el uid del usuario autenticado, desde este componente, puedo enviar el atributo de correo electrónico en la colección de autenticación para este usuario.
Mientras que la documentación de Firebase proporciona este consejo para administrar usuarios y acceder a los atributos aquí, no he encontrado una manera de implementar este enfoque en react. Cada variación de un intento de hacer esto, tanto al hacer ayudantes en mi firebase.js como al intentar comenzar desde cero en un componente produce errores al acceder a firebase. Sin embargo, puedo producir una lista de usuarios y su información de recopilación de usuarios relacionada (no puedo obtener un usuario en función de quién es el authUser).
Mi componente de clase tiene:
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
} from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import { compose } from 'recompose';
import { withFirebase } from '../Firebase/Index';
import { AuthUserContext, withAuthorization, withEmailVerification } from '../Session/Index';
class Dashboard extends React.Component {
state = {
collapsed: false,
};
onCollapse = collapsed => {
console.log(collapsed);
this.setState({ collapsed });
};
render() {
const { loading } = this.state;
// const dbUser = this.props.firebase.app.snapshot.data();
// const user = Firebase.auth().currentUser;
return (
<AuthUserContext.Consumer>
{authUser => (
<div>
{authUser.email} // error message as shown above
{console.log(authUser)} // output logged in amongst a long list of menus prefixed with either 1 or 2 characters. I can't find a key to decipher what these menus mean or do.
</div>
)}
</AuthUserContext.Consumer>
);
}
}
//export default withFirebase(Dashboard);
export default Dashboard;
En mi AuthContext.Provider, tengo:
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;
PRÓXIMO INTENTO
Es realmente extraño que con este intento, estoy tratando de registrar en la consola los valores que veo que existen en la base de datos y el valor del nombre se devuelve como 'indefinido' donde el db tiene una cadena.
Este intento tiene:
import React from 'react';
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 } from '../Session/Index';
class Dash extends React.Component {
// state = {
// collapsed: false,
// };
constructor(props) {
super(props);
this.state = {
collapsed: false,
loading: false,
user: null,
...props.location.state,
};
}
componentDidMount() {
if (this.state.user) {
return;
}
this.setState({ loading: true });
this.unsubscribe = this.props.firebase
.user(this.props.match.params.id)
// .user(this.props.user.uid)
// .user(authUser.uid)
// .user(authUser.id)
// .user(Firebase.auth().currentUser.id)
// .user(Firebase.auth().currentUser.uid)
.onSnapshot(snapshot => {
this.setState({
user: snapshot.data(),
loading: false,
});
});
}
componentWillUnmount() {
this.unsubscribe && this.unsubscribe();
}
onCollapse = collapsed => {
console.log(collapsed);
this.setState({ collapsed });
};
render() {
// const { loading } = this.state;
const { user, loading } = this.state;
// let match = useRouteMatch();
// const dbUser = this.props.firebase.app.snapshot.data();
// const user = Firebase.auth().currentUser;
return (
<AuthUserContext.Consumer>
{authUser => (
<div>
{loading && <div>Loading ...</div>}
<Layout style={{ minHeight: '100vh' }}>
<Sider collapsible collapsed={this.state.collapsed} onCollapse={this.onCollapse}>
<div />
</Sider>
<Layout>
<Header>
{console.log("authUser:", authUser)}
// this log returns the big long list of outputs - the screen shot posted above is an extract. It includes the correct Authentication table (collection) attributes
{console.log("authUser uid:", authUser.uid)}
// this log returns the correct uid of the current logged in user
{console.log("Current User:", this.props.firebase.auth.currentUser.uid)}
// this log returns the correct uid of the current logged in user
{console.log("current user:", this.props.firebase.db.collection("users").doc(this.props.firebase.auth.currentUser.uid
))}
// this log returns a big long list of things under a heading: DocumentReference {_key: DocumentKey, firestore: Firestore, _firestoreClient: FirestoreClient}. One of the attributes is: id: (...) (I can't click to expand this).
{console.log("current user:", this.props.firebase.db.collection("users").doc(this.props.firebase.auth.currentUser.uid
).name)}
//this log returns: undefined. There is an attribute in my user document called 'name'. It has a string value on the document with the id which is the same as the currentUser.uid.
<Text style={{ float: 'right', color: "#fff"}}>
{user && (
<Text style={{ color: "#fff"}}>{user.name}
//this just gets skipped over in the output. No error but also does not return the name.
</Text>
)}
</Text>
</Header>
</Layout>
</Layout>
</div>
)}
</AuthUserContext.Consumer>
);
}
}
export default withFirebase(Dash);
PRÓXIMO INTENTO
Por lo tanto, este intento es torpe y no utiliza los ayudantes ni las consultas de instantáneas que intenté usar anteriormente, sino que registra los atributos del documento de recopilación de usuarios en la consola de la siguiente manera:
{this.props.firebase.db.collection ('usuarios'). doc (authUser.uid) .get ()
.then(doc => {
console.log(doc.data().name)
})
}
Sin embargo, lo que no puedo hacer es encontrar una manera de representar ese nombre en el jsx
¿Cómo se imprime realmente la salida?
Cuando lo intento:
{
this.props.firebase.db.collection('users').doc(authUser.uid).get().data().name
}
Me sale un error que dice:
TypeError: this.props.firebase.db.collection (...). Doc (...). Get (...). Data no es una función
Cuando lo intento:
{
this.props.firebase.db.collection('users').doc(authUser.uid).get()
.then(doc => {
console.log(doc.data().name),
<p>doc.data().name</p>
})
}
Me sale un error que dice:
Línea 281: 23: esperaba una llamada de asignación o función y en su lugar vio una expresión expresiones no utilizadas
Cuando lo intento:
{
this.props.firebase.db.collection('users').doc(authUser.uid).get("name")
.then(doc => {
console.log(doc.data().name),
<p>doc.data().name</p>
})
}
El mensaje de error dice:
Esperaba una asignación o llamada de función y en su lugar vio una expresión
Estoy listo para renunciar a tratar de averiguar cómo hacer que funcionen las consultas de instantáneas, si solo puedo obtener el nombre de la colección de usuarios para representar en la pantalla. ¿Alguien puede ayudar con ese paso?
PRÓXIMO INTENTO
Encontré esta publicación . Tiene una buena explicación de lo que debe suceder, pero no puedo implementarlo como se muestra porque componentDidMount no sabe qué es authUser.
Mi intento actual es el siguiente: sin embargo, como está escrito actualmente, authUser es un contenedor del valor de retorno, y el segmento componentDidMount no sabe qué es authUser.
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
useRouteMatch,
} from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import { compose } from 'recompose';
import { Divider, Layout, Card, Tabs, Typography, Menu, Breadcrumb, Icon } from 'antd';
import { withFirebase } from '../Firebase/Index';
import { AuthUserContext, withAuthorization, withEmailVerification } from '../Session/Index';
const { Title, Text } = Typography
const { TabPane } = Tabs;
const { Header, Content, Footer, Sider } = Layout;
const { SubMenu } = Menu;
class Dashboard extends React.Component {
// state = {
// collapsed: false,
// loading: false,
// };
constructor(props) {
super(props);
this.state = {
collapsed: false,
loading: false,
user: null,
...props.location.state,
};
}
componentDidMount() {
if (this.state.user) {
return;
}
this.setState({ loading: true });
this.unsubscribe = this.props.firebase
.user(this.props.match.params.id)
.onSnapshot(snapshot => {
this.setState({
user: snapshot.data(),
loading: false,
});
});
// }
// firebase.firestore().collection("users")
// .doc(this.state.uid)
// .get()
// .then(doc => {
// this.setState({ post_user_name: doc.data().name });
// });
// }
this.props.firebase.db
.collection('users')
.doc(authUser.uid)
.get()
.then(doc => {
this.setState({ user_name: doc.data().name });
// loading: false,
});
}
componentWillUnmount() {
this.unsubscribe && this.unsubscribe();
}
onCollapse = collapsed => {
console.log(collapsed);
this.setState({ collapsed });
};
render() {
// const { loading } = this.state;
// const { user, loading } = this.state;
// let match = useRouteMatch();
// const dbUser = this.props.firebase.app.snapshot.data();
// const user = Firebase.auth().currentUser;
return (
<AuthUserContext.Consumer>
{ authUser => (
<div>
<Header>
{/*
{
this.props.firebase.db.collection('users').doc(authUser.uid).get()
.then(doc => {
console.log( doc.data().name
)
})
}
*/}
</Text>
</Header>
<Switch>
</Switch>
</div>
)}
</AuthUserContext.Consumer>
);
}
}
export default withFirebase(Dashboard);
PRÓXIMO INTENTO
A continuación, intenté envolver la ruta para el tablero dentro del AuthContext.Consumer para que todo el componente pudiera usarlo, lo que me permitió acceder al usuario conectado en la función componentDidMount.
Cambié la ruta a:
<Route path={ROUTES.DASHBOARD} render={props => (
<AuthUserContext.Consumer>
{ authUser => (
<Dashboard authUser={authUser} {...props} />
)}
</AuthUserContext.Consumer>
)} />
y eliminó al consumidor de la declaración de representación del componente del tablero.
Luego, en el componenteDidMount en el componente Tablero, probé:
componentDidMount() {
if (this.state.user) {
return;
}
this.setState({ loading: true });
this.unsubscribe =
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,
});
}
Cuando intento esto, aparece un error que dice:
FirebaseError: Function CollectionReference.doc () requiere que su primer argumento sea de tipo cadena no vacía, pero fue: un objeto DocumentReference personalizado
PRÓXIMO INTENTO Las personas a continuación parecen encontrar algo útil en la primera solución propuesta. No he podido encontrar nada útil en él, pero al volver a leer sus sugerencias, me cuesta ver cómo funciona el ejemplo en la documentación de Firebase (no revela cómo dar un valor: uid a la solicitud .doc () ), que es el siguiente:
db.collection("cities").doc("SF");
docRef.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
es fundamentalmente diferente a mi intento en la función componentDidMount, que es:
this.unsubscribe =
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').uid: this.props.firebase.auth().currentUser.uid )
.doc(this.props.authUser.uid)
.get()
.then(doc => {
this.setState({ user.name: doc.data().name });
// loading: false,
}else {
// doc.data() will be undefined in this case
console.log("Can't find this record");
}
);
}
Quizás resolver ese paso es una pista que ayudará a avanzar hacia un resultado. ¿Alguien puede encontrar una mejor documentación de la tienda de incendios para mostrar cómo obtener un registro de recopilación de usuarios utilizando un uid de escucha de usuario conectado?
Con ese fin, puedo ver en el ejemplo del laboratorio de códigos FriendlyEats , que hay un intento de dar un doc.id al valor de búsqueda de id en el código. No sé en qué idioma está escrito este código, pero se parece a lo que estoy tratando de hacer, simplemente no puedo ver cómo pasar de ese ejemplo a algo con lo que sé cómo trabajar.
display: function(doc) {
var data = doc.data();
data['.id'] = doc.id;
data['go_to_restaurant'] = function() {
that.router.navigate('/restaurants/' + doc.id);
};
Respuestas:
Entiendo desde la última línea de su pregunta (
users = () => this.db.collection('users');
) que se llama a la colección donde almacena información adicional sobre los usuariosusers
y que un documento de usuario en esta colección usa el ID de usuario (uid) como documento.Lo siguiente debería hacer el truco (no probado):
Entonces, dentro del observador establecido a través del
onAuthStateChanged()
método, cuando detectamos que el usuario ha iniciado sesión (es decir, enif (authUser) {}
), usamos suuid
para consultar el documento único correspondiente a este usuario en lausers
colección (ver leer un documento y el documento delget()
método).fuente
this.auth.onAuthStateChanged(authUser => { if (authUser) {console.log(authUser.uid) })
?Tengo una teoría que me gustaría que probaras.
Creo que cuando llamas
next(authUser)
dentro de tuonAuthStateChanged
controlador, durante su ejecución encuentra un error (comocannot read property 'name' of undefined at ...
).La razón por la que su código no funciona como se esperaba es porque donde llama
next(authUser)
, está dentrothen()
de una cadena Promise. Cualquier error arrojado dentro de una Promesa será detectado y hará que la Promesa sea rechazada. Cuando se rechaza una Promesa, llamará a sus controladores de error adjuntos con el error. La cadena de Promesa en cuestión actualmente no tiene ningún controlador de errores.Si te he perdido, lee esta publicación de blog para un curso intensivo de Promesas y luego regresa.
Entonces, ¿qué podemos hacer para evitar tal situación? Lo más sencillo sería llamar
next(authUser)
fuera delthen()
alcance del controlador Promise . Podemos hacer esto usandowindow.setTimeout(function)
.Entonces, en su código, reemplazaría
con
Esto arrojará cualquier error como normal en lugar de ser atrapado por la cadena Promise.
Es importante destacar que no tiene un controlador de captura que maneja cuando
userDocRef.get()
falla. Así que solo agregue.catch(() => setTimeout(fallback))
al final dethen()
modo que su código use el método alternativo si se produce un error.Entonces terminamos con:
Código editado
La explicación anterior debería permitirle corregir su código, pero aquí está mi versión de su
Firebase
clase con varios cambios de facilidad de uso.Uso:
Definición de clase:
Tenga en cuenta que en esta versión, el
onUserDataListener
método devuelve la función de cancelación de suscripciónonAuthStateChanged
. Cuando su componente se desmonta, debe separar todos los oyentes relevantes para que no se pierda la memoria o se ejecute un código roto en segundo plano cuando no sea necesario.fuente
console.log(snapshot.data())
?En su
AuthContext.Provider
implementación, accedeonAuthStateChanged
directamente al oyente del SDK :Esto debe cambiarse para usar el
onAuthUserListener
de su clase auxiliar:Con respecto a los mensajes de registro llenos de muchas propiedades aleatorias, esto se debe a que el
firebase.User
objeto tiene una API pública y una implementación con varias propiedades y métodos privados que se minimizan cuando se compilan. Debido a que estas propiedades y métodos minimizados no están marcados explícitamente como no enumerables, se incluyen en cualquier salida de registro. Si, en cambio, solo desea registrar las partes que son realmente útiles, puede desestructurar y reestructurar el objeto usando:fuente
Dashboard
archivo esexport default withAuthentication(Dashboard);
(y nowithFirebase
)Si alguien más está atascado de manera similar, encontré la solución aquí: Firebase & React: CollectionReference.doc () tipo de argumento
No funciona en la actualización de la página (aún arroja un error de que el uid es nulo) pero los ganchos de reacción para usar Effect deben reemplazar la función componentDidMount con una combinación de Mount y Update. Estoy intentando eso a continuación.
fuente