J'essaie de comprendre comment obtenir un nom d'utilisateur qui est un attribut stocké dans une collection d'utilisateurs, qui a été fusionné avec les attributs créés par le modèle d'authentification Firebase.
Je peux accéder à authUser - ce qui me donne les champs limités que Firebase recueille dans l'outil d'authentification, puis j'essaie d'accéder à la collection d'utilisateurs associée (qui utilise le même UID).
J'ai un consommateur de contexte réactif avec:
import React from 'react';
const AuthUserContext = React.createContext(null);
export default AuthUserContext;
Ensuite, dans mon composant, j'essaie d'utiliser:
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);
Dans mon firebase.js - je pense que j'ai essayé de fusionner les attributs authUser du modèle d'authentification avec les attributs de la collection d'utilisateurs 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();
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();
}
});
Je ne peux pas trouver un moyen d'obtenir de l'authUser (qui fonctionne pour me rendre aux attributs d'authentification) - à la collection d'utilisateurs qui a un id avec le même uid de la collection d'authentification.
J'ai vu ce message , qui semble avoir le même problème et j'ai essayé de comprendre ce que la réponse est censée suggérer - mais je n'arrive pas à trouver un moyen qui fonctionne pour passer de la collection d'authentification à la collection d'utilisateurs et je ne sais pas ce que la fusion fait pour moi si elle ne me donne pas accès aux attributs de la collection d'utilisateurs depuis authUser.
J'ai essayé d'utiliser un assistant dans mon firebase.js pour me donner un utilisateur à partir d'un UID - mais cela ne semble pas aider non plus.
user = uid => this.db.doc(`users/${uid}`);
users = () => this.db.collection('users');
Prochaine tentative
Pour ajouter plus de fond, j'ai créé un composant de test qui peut enregistrer (mais pas restituer) l'authUser, comme suit:
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;
Le journal affiche les détails de l'uid, du courrier électronique, etc. dans les journaux, mais il se trouve parmi une longue liste d'éléments - dont beaucoup sont préfacés avec 1 ou 2 lettres (je ne trouve pas de clé pour savoir ce que chacun de ces préfixes lettres signifient). Exemple extrait ci-dessous:
MISE À JOUR SUR CE COMMENTAIRE:
Auparavant, je disais: les champs pour uid, email, etc. ne semblent pas être imbriqués sous ces préfixes, mais si j'essaie de:
console.log(authUser.email)
, J'obtiens une erreur qui dit:
TypeError: Impossible de lire la propriété 'e-mail' de null
La mise à jour: Je viens de réaliser que dans le journal de la console, je dois développer un menu déroulant qui est étiqueté:
Q {I: tableau (0), l:
pour voir l'attribut email. Quelqu'un sait-il à quoi ce charabia fait allusion? Je ne trouve pas de clé pour comprendre ce que signifie Q, I ou l, pour savoir si je suis censé faire référence à ces choses pour accéder aux attributs pertinents dans la table d'authentification. Peut-être que si je peux comprendre cela - je peux trouver un moyen d'accéder à la collection d'utilisateurs en utilisant l'uid de la collection d'authentification.
Quelqu'un at-il utilisé réagir sur le front-end, avec un consommateur de contexte pour savoir qui est l'utilisateur actuel? Si oui, comment accédez-vous à leurs attributs sur le modèle d'authentification et comment avez-vous accédé aux attributs de la collection User associée (où le docId sur le document User est l'uid de la table d'authentification)?
PROCHAINE TENTATIVE
La prochaine tentative a produit un résultat très étrange.
J'ai 2 pages distinctes qui sont des consommateurs de contexte. La différence entre eux est que l'un est une fonction et l'autre est un composant de classe.
Dans le composant fonction, je peux rendre {authUser.email}. Lorsque j'essaie de faire la même chose dans le composant classe, j'obtiens une erreur qui dit:
TypeError: Impossible de lire la propriété 'e-mail' de null
Cette erreur provient de la même session avec le même utilisateur connecté.
Remarque: bien que la documentation de Firebase indique qu'une propriété currentUser est disponible sur auth - je n'ai pas du tout réussi à le faire fonctionner.
Mon composant de fonction a:
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;
Bien que je ne puisse pas accéder aux attributs de la collection User où le docId sur le document utilisateur est le même que l'uid de l'utilisateur authentifié, à partir de ce composant, je peux sortir l'attribut email sur la collection auth pour cet utilisateur.
Bien que la documentation de Firebase fournisse ces conseils pour gérer les utilisateurs et accéder aux attributs ici, je n'ai pas trouvé de moyen de mettre en œuvre cette approche dans React. Chaque variation d'une tentative de faire cela, à la fois en créant des assistants dans mon firebase.js et en essayant de recommencer à zéro dans un composant produit des erreurs dans l'accès à firebase. Je peux cependant produire une liste d'utilisateurs et leurs informations de collecte d'utilisateurs associées (je ne peux pas obtenir un utilisateur en fonction de qui est l'authUser).
Mon composant de classe a:
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;
Dans mon AuthContext.Provider - j'ai:
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;
PROCHAINE TENTATIVE
Il est vraiment étrange qu'avec cette tentative, j'essaie de consigner les valeurs que je peux voir exister dans la base de données et la valeur de nom est renvoyée comme 'non définie' où la base de données contient une chaîne.
Cette tentative a:
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);
PROCHAINE TENTATIVE
Cette tentative est donc maladroite et n'utilise pas les assistants ou les requêtes d'instantanés que j'ai essayé d'utiliser ci-dessus, mais enregistrez les attributs du document de collection d'utilisateurs sur la console comme suit:
{this.props.firebase.db.collection ('utilisateurs'). doc (authUser.uid) .get ()
.then(doc => {
console.log(doc.data().name)
})
}
Ce que je ne peux pas faire, c'est trouver un moyen de rendre ce nom dans le jsx
Comment imprimez-vous réellement la sortie?
Quand j'essaye:
{
this.props.firebase.db.collection('users').doc(authUser.uid).get().data().name
}
Je reçois une erreur qui dit:
TypeError: this.props.firebase.db.collection (...). Doc (...). Get (...). Data n'est pas une fonction
Quand j'essaye:
{
this.props.firebase.db.collection('users').doc(authUser.uid).get()
.then(doc => {
console.log(doc.data().name),
<p>doc.data().name</p>
})
}
Je reçois une erreur qui dit:
Ligne 281: 23: Attendu une affectation ou un appel de fonction et vu à la place une expression expressions non-inutilisées
Quand j'essaye:
{
this.props.firebase.db.collection('users').doc(authUser.uid).get("name")
.then(doc => {
console.log(doc.data().name),
<p>doc.data().name</p>
})
}
Le message d'erreur dit:
Attendu une affectation ou un appel de fonction et vu à la place une expression
Je suis prêt à renoncer à essayer de savoir comment faire fonctionner les requêtes de clichés - si je peux simplement obtenir le nom de la collection d'utilisateurs à afficher à l'écran. Quelqu'un peut-il aider à cette étape?
PROCHAINE TENTATIVE
J'ai trouvé ce post . Il a une bonne explication de ce qui doit se produire, mais je ne peux pas l'implémenter comme indiqué car componentDidMount ne sait pas ce qu'est l'authUser.
Ma tentative actuelle est la suivante - cependant, tel qu'écrit actuellement, authUser est un wrapper sur la valeur de retour - et le segment componentDidMount ne sait pas ce qu'est 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);
PROCHAINE TENTATIVE
Ensuite, j'ai essayé d'enrouler la route du tableau de bord dans AuthContext.Consumer afin que l'ensemble du composant puisse l'utiliser - me permettant ainsi d'accéder à l'utilisateur connecté dans la fonction componentDidMount.
J'ai changé l'itinéraire pour:
<Route path={ROUTES.DASHBOARD} render={props => (
<AuthUserContext.Consumer>
{ authUser => (
<Dashboard authUser={authUser} {...props} />
)}
</AuthUserContext.Consumer>
)} />
et supprimé le consommateur de l'instruction de rendu du composant de tableau de bord.
Puis dans le componentDidMount sur le composant Dashboard, j'ai essayé:
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,
});
}
Lorsque j'essaye ceci, j'obtiens une erreur qui dit:
FirebaseError: La fonction CollectionReference.doc () requiert que son premier argument soit de type chaîne non vide, mais il s'agissait: d'un objet DocumentReference personnalisé
PROCHAINE TENTATIVE Les personnes ci-dessous semblent trouver quelque chose d'utile dans la première solution proposée. Je n'ai rien trouvé d'utile, mais en relisant ses suggestions, j'ai du mal à voir comment l'exemple dans la documentation de Firebase (il ne révèle pas comment donner une valeur: uid à la demande .doc () ), qui est le suivant:
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!");
}
est fondamentalement différent de ma tentative dans la fonction componentDidMount, qui est:
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");
}
);
}
Peut-être que la résolution de cette étape est un indice qui aidera à faire avancer cela vers un résultat. Quelqu'un peut-il trouver une meilleure documentation Firestore pour montrer comment obtenir un enregistrement de collection d'utilisateurs à l'aide d'un UID d'écoute d'utilisateur connecté?
À cette fin, je peux voir dans l' exemple de laboratoire de code FriendlyEats , qu'il y a une tentative de donner un doc.id à la valeur de recherche d'ID dans le code. Je ne sais pas dans quelle langue ce code est écrit - mais il ressemble à ce que j'essaie de faire - je ne vois tout simplement pas comment passer de cet exemple à quelque chose avec lequel je sais travailler.
display: function(doc) {
var data = doc.data();
data['.id'] = doc.id;
data['go_to_restaurant'] = function() {
that.router.navigate('/restaurants/' + doc.id);
};
Réponses:
Je comprends de la dernière ligne de votre question (
users = () => this.db.collection('users');
) que la collection où vous stockez des informations supplémentaires sur les utilisateurs est appeléeusers
et qu'un document utilisateur de cette collection utilise le userId (uid) comme docId.Ce qui suit devrait faire l'affaire (non testé):
Ainsi, au sein de l'observateur défini par la
onAuthStateChanged()
méthode, lorsque nous détectons que l'utilisateur est connecté (c'est-à-dire dansif (authUser) {}
), nous l'utilisonsuid
pour interroger le document unique correspondant à cet utilisateur dans lausers
collection (voir lire un document et le document duget()
méthode).la source
this.auth.onAuthStateChanged(authUser => { if (authUser) {console.log(authUser.uid) })
?J'ai une théorie que j'aimerais que vous testiez.
Je pense que lorsque vous appelez
next(authUser)
à l' intérieur de votreonAuthStateChanged
gestionnaire, lors de son exécution, il rencontre une erreur (telle quecannot read property 'name' of undefined at ...
).La raison pour laquelle votre code ne fonctionne pas comme prévu, c'est que lorsque vous appelez
next(authUser)
, il se trouve à l'intérieurthen()
d'une chaîne Promise. Toute erreur lancée à l'intérieur d'une promesse sera détectée et entraînera le rejet de la promesse. Lorsqu'une promesse est rejetée, elle appelle ses gestionnaires d'erreurs attachés avec l'erreur. La chaîne Promise en question n'a actuellement aucun gestionnaire d'erreur de ce type.Si je vous ai perdu, lisez cet article de blog pour un cours intensif Promises, puis revenez.
Alors, que pouvons-nous faire pour éviter une telle situation? Le plus simple serait d'appeler en
next(authUser)
dehors de lathen()
portée du gestionnaire Promise . Nous pouvons le faire en utilisantwindow.setTimeout(function)
.Donc, dans votre code, vous remplaceriez
avec
Cela générera des erreurs comme normales plutôt que d'être pris par la chaîne Promise.
Surtout, vous n'avez pas de gestionnaire de capture qui gère les
userDocRef.get()
échecs. Il suffit donc d'ajouter.catch(() => setTimeout(fallback))
à la fin dethen()
afin que votre code utilise la méthode de secours en cas d'erreur.On se retrouve donc avec:
Code édité
L'explication ci-dessus devrait vous permettre de corriger votre code, mais voici ma version de votre
Firebase
classe avec divers changements de facilité d'utilisation.Usage:
Définition de classe:
Notez que dans cette version, la
onUserDataListener
méthode renvoie la fonction de désabonnement deonAuthStateChanged
. Lorsque votre composant est démonté, vous devez détacher tous les écouteurs pertinents afin de ne pas avoir de fuite de mémoire ou de code cassé s'exécutant en arrière-plan lorsqu'il n'est pas nécessaire.la source
console.log(snapshot.data())
?Dans votre
AuthContext.Provider
implémentation, vous accédezonAuthStateChanged
directement à l'écouteur du SDK :Cela devrait être modifié pour utiliser le
onAuthUserListener
de votre classe d'assistance:En ce qui concerne les messages de journal remplis de nombreuses propriétés aléatoires, cela est dû au fait que l'
firebase.User
objet possède à la fois une API publique et une implémentation avec un certain nombre de propriétés et de méthodes privées qui sont minifiées lors de la compilation. Étant donné que ces propriétés et méthodes minifiées ne sont pas explicitement marquées comme non énumérables, elles sont incluses dans toute sortie de journal. Si vous souhaitez plutôt consigner uniquement les parties réellement utiles, vous pouvez détruire et restructurer l'objet en utilisant:la source
Dashboard
fichier estexport default withAuthentication(Dashboard);
(et nonwithFirebase
)Si quelqu'un d'autre est bloqué de la même manière, j'ai trouvé la solution ici: Firebase & React: Type d'argument CollectionReference.doc ()
Cela ne fonctionne pas lors de l'actualisation de la page (génère toujours une erreur indiquant que l'uid est nul), mais les hooks de réaction à useEffect devraient remplacer la fonction componentDidMount par une combinaison de montage et de mise à jour. J'essaye ça ensuite.
la source