Comment obtenir les détails de la base de données utilisateur liés à un utilisateur dans Firestore?

10

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:

entrez la description de l'image ici

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);
      };
Mel
la source
Pour votre information, votre terminologie n'est pas tout à fait correcte, ce qui rend cette question difficile à lire. Il n'y a rien dans Firebase appelé "table". Dans Firebase Auth, il n'y a que des utilisateurs - pas de "table d'authentification". Dans Firestore, il existe des collections et des documents dans ces collections, mais pas de tableaux. J'essaie de comprendre où vous êtes bloqué et comment le code que vous avez montré ne fonctionne pas comme vous l'attendez, mais je ne le rassemble pas. Envisagez de modifier la question pour utiliser une terminologie plus standard que celle que vous trouveriez dans les documents, et soyez plus clair sur ce qui ne fonctionne pas comme prévu.
Doug Stevenson
Très bien - heureux de substituer des tables aux collections. Le point est toujours le même.
Mel
Mon point principal est que je n'ai pas vraiment pu comprendre votre point, et la terminologie n'a pas aidé. Pourriez-vous expliquer plus en détail ce qui ne fonctionne pas dans le code que vous avez montré? Quelque chose n'a-t-il pas fonctionné comme prévu? Des erreurs ou des journaux de débogage pour illustrer?
Doug Stevenson
Rien ne fonctionne. Je m'attends à trouver un moyen d'accéder aux détails de la collection d'utilisateurs à partir de l'écouteur authUser. authUser est défini dans le gestionnaire de contexte et la méthode de classe associée qui écoute les modifications de la méthode. Je ne parviens pas à dépasser les attributs de la collection d'authentification - j'essaie d'obtenir l'accès à la collection d'utilisateurs associée dans Firestore. Pas de journaux. Juste des messages d'erreur indiquant que le champ n'est pas défini.
Mel
1
Je suggère de commencer par une tâche simple, de la faire fonctionner afin d'acquérir une certaine expérience, puis de l'appliquer à des problèmes plus complexes comme celui que vous avez actuellement. Il n'y a rien de fondamentalement incorrect dans la documentation (je sais, parce que je l'utilise tout le temps, et j'aide les gens avec la même chose). Afin d'obtenir de l'aide sur Stack Overflow, vous devrez illustrer un problème spécifique, idéalement un MCVE que n'importe qui peut utiliser pour reproduire le problème. Il ne suffit pas de dire "je ne peux pas le faire fonctionner". stackoverflow.com/help/minimal-reproducible-example
Doug Stevenson

Réponses:

5

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ée userset qu'un document utilisateur de cette collection utilise le userId (uid) comme docId.

Ce qui suit devrait faire l'affaire (non testé):

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.db.collection('users').doc(authUser.uid)
              .get()
              .then(snapshot => {
                const userData = snapshot.data();
                console.log(userData);
                //Do whatever you need with userData
                //i.e. merging it with authUser
                //......

                next(authUser);
          });
      } else {
        fallback();
      }
    });

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 dans if (authUser) {}), nous l'utilisons uidpour interroger le document unique correspondant à cet utilisateur dans la userscollection (voir lire un document et le document du get()méthode).

Renaud Tarnec
la source
Y a-t-il quelque chose qui ne va pas dans la façon dont j'ai défini onAuthUserListener? Ensuite, si j'essaie vos amendements à cette méthode, que dois-je faire pour accéder à la collection d'utilisateurs depuis authUser?
Mel
"Donc, y a-t-il quelque chose qui ne va pas dans la façon dont j'ai défini onAuthUserListener?" -> pas d'après ce que je peux voir. "que dois-je faire pour accéder à la collection d'utilisateurs depuis authUser?" -> Si je comprends bien, vous voulez obtenir UN document, pas la collection. Le code dans la réponse devrait fonctionner.
Renaud Tarnec
Je veux obtenir l'authUser - votre code est-il une amélioration de ma tentative? Je ne trouve pas de moyen qui permette à l'authUser de me donner accès à la collection d'utilisateurs qui a le même uid. J'essaie de comprendre votre suggestion de code - pour savoir comment cela améliore le mien dans un premier temps. Pouvez-vous s'il vous plaît identifier quelle partie de cette amélioration / correction? Merci
Mel
Que se passe-t-il si vous le faites this.auth.onAuthStateChanged(authUser => { if (authUser) {console.log(authUser.uid) })?
Renaud Tarnec
Je peux sortir toutes les propriétés authUser (les données de collecte d'authentification). Je ne peux pas accéder aux données utilisateur à partir du document associé dans la collection d'utilisateurs dont l'ID est l'ID
Mel
1

J'ai une théorie que j'aimerais que vous testiez.

Je pense que lorsque vous appelez next(authUser)à l' intérieur de votre onAuthStateChangedgestionnaire, lors de son exécution, il rencontre une erreur (telle que cannot 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érieur then()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 la then()portée du gestionnaire Promise . Nous pouvons le faire en utilisant window.setTimeout(function).

Donc, dans votre code, vous remplaceriez

next(authUser)

avec

setTimeout(() => next(authUser))
// or setTimeout(() => next(authUser), 0) for the same result

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 de then()afin que votre code utilise la méthode de secours en cas d'erreur.

On se retrouve donc avec:

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 = {
      ...dbUser, // CHANGED: Moved dbUser to beginning so it doesn't override other info
      uid: authUser.uid,
      email: authUser.email,
      emailVerified: authUser.emailVerified,
      providerData: authUser.providerData
    };
    setTimeout(() => next(authUser), 0); // invoke callback outside of Promise
  })
  .catch((err) => setTimeout(() => fallback(), 0)); // invoke callback outside of Promise

Code édité

L'explication ci-dessus devrait vous permettre de corriger votre code, mais voici ma version de votre Firebaseclasse avec divers changements de facilité d'utilisation.

Usage:

import FirebaseHelper from './FirebaseHelper.js';

const fb = new FirebaseHelper();
fb.onUserDataListener(userData => {
  // do something - user is logged in!
}, () => {
  // do something - user isn't logged in or an error occurred
}

Définition de classe:

// granular Firebase namespace import
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

const config = { /* firebase config goes here */ };

export default class FirebaseHelper { // renamed from `Firebase` to prevent confusion
  constructor() {
    /* init SDK if needed */
    if (firebase.apps.length == 0) { firebase.initializeApp(config); }

    /* helpers */
    this.fieldValue = app.firestore.FieldValue;

    /* Firebase APIs */
    this.auth = firebase.auth();
    this.db = firebase.firestore();
  }

  getUserDocRef(uid) { // renamed from `user`
    return this.db.doc(`users/${uid}`);
  }

  getUsersColRef() { // renamed from `users`
    return this.db.collection('users');
  }

  /**
   * Attaches listeners to user information events.
   * @param {function} next - event callback that receives user data objects
   * @param {function} fallback - event callback that is called on errors or when user not logged in
   *
   * @returns {function} unsubscribe function for this listener
   */
  onUserDataListener(next, fallback) {
    return this.auth.onAuthStateChanged(authUser => {
      if (!authUser) {
        // user not logged in, call fallback handler
        fallback();
        return;
      }

      this.getUserDocRef(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('Error while getting user document -> ', err.code ? err.code + ': ' + err.message : (err.message || err));
          setTimeout(fallback, 0); // escapes this Promise's error handler
        });
    });
  }

  // ... other methods ...
}

Notez que dans cette version, la onUserDataListenerméthode renvoie la fonction de désabonnement de onAuthStateChanged. 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.

class SomeComponent {
  constructor() {
    this._unsubscribe = fb.onUserDataListener(userData => {
      // do something - user is logged in!
    }, () => {
      // do something - user isn't logged in or an error occurred
    };
  }

  // later
  componentWillUnmount() {
    this._unsubscribe();
  }
}
samthecodingman
la source
Je vous remercie! Je vais essayer ça ce soir. Je suis ravi d'en savoir plus de toute façon. Je reviendrai sous peu avec des commentaires.
Mel
Salut Sam - merci d'avoir proposé cette suggestion. J'ai pris un peu de temps pour lire la documentation que vous avez liée et j'ai eu quelques essais. Bien que je sois reconnaissant de l'aide - cela n'a pas résolu mon problème. Lorsque j'essaie d'accéder aux attributs de la collection d'utilisateurs, j'obtiens toujours une erreur qui dit: TypeError: Impossible de lire la propriété 'user' d'undefined
Mel
@Mel Lorsque vous avez exécuté le code d'origine, avez-vous été informé de TypeError? C'est la première fois que vous en parlez. Ce qui signifie que ce code a fait son travail de jeter l'erreur en dehors de la portée de la promesse. Pouvez-vous fournir la sortie de console.log(snapshot.data())?
samthecodingman
J'ai essayé ceci - le message d'erreur dit: TypeError: snapshot.data n'est pas une fonction
Mel
Je vais continuer d'essayer de le déplacer - peut-être que je n'essaye pas de le connecter au bon endroit
Mel
0

Dans votre AuthContext.Providerimplémentation, vous accédez onAuthStateChanged directement à l'écouteur du SDK :

componentDidMount() {
  this.listener = this.props.firebase.auth.onAuthStateChanged(
    authUser => {
      authUser
        ? this.setState({ authUser })
        : this.setState({ authUser: null });
    }
  );
}

Cela devrait être modifié pour utiliser le onAuthUserListenerde votre classe d'assistance:

componentDidMount() {
  this.listener = this.props.firebase.onAuthUserListener(
    /* next()     */ (authUserWithData) => this.setState({authUser: authUserWithData}),
    /* fallback() */ () => this.setState({authUser: null})
  );
}

En ce qui concerne les messages de journal remplis de nombreuses propriétés aléatoires, cela est dû au fait que l' firebase.Userobjet 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:

// Extracts public properties of firebase.User objects
// see https://firebase.google.com/docs/reference/js/firebase.User#properties
function extractPublicProps(user) {
  let {displayName, email, emailVerified, isAnonymous, metadata, phoneNumber, photoURL, providerData, providerId, refreshToken, tenantId, uid} = user;
  return {displayName, email, emailVerified, isAnonymous, metadata, phoneNumber, photoURL, providerData, providerId, refreshToken, tenantId, uid}
}

function extractUsefulProps(user) {
  let {displayName, email, emailVerified, isAnonymous, phoneNumber, photoURL, uid} = user;
  return {displayName, email, emailVerified, isAnonymous, phoneNumber, photoURL, uid}
}

let authUser = firebase.auth().currentUser;
console.log(authUser);
console.log(extractPublicProps(authUser));
console.log(extractUsefulProps(authUser));
samthecodingman
la source
Merci @samthecodingman d'avoir continué à essayer de m'aider. J'ai regardé ça. Mon objectif est de lire l'uid de l'authUser afin de pouvoir l'utiliser pour obtenir les attributs de la collection d'utilisateurs associée pour cet utilisateur (le nom dans la collection d'utilisateurs a plus que displayName dans la collection d'authentification Firebase - donc je n'essaye pas de lire les propriétés de la table d'authentification
Mel
La modification suggérée à la fonction d'écouteur componentDidMount n'a pas généré d'erreur - mais cela n'a pas fonctionné. Lorsqu'il essaie de consigner la valeur d'authUser dans le composant de tableau de bord à l'aide de cet écouteur, j'obtiens une erreur indiquant que authUser n'est pas défini. Je n'obtiens pas cette erreur lorsque j'utilise le componentDidMount pour AuthContext.Provider de la façon dont je l'avais défini. Merci pour les informations sur les propriétés aléatoires dans les messages du journal.
Mel
@Mel Pouvez-vous confirmer que la dernière ligne de votre Dashboardfichier est export default withAuthentication(Dashboard);(et non withFirebase)
samthecodingman
Confirmé. withFirebase est incorporé dans withAuthentication - il est donc récupéré via ce HOC.
Mel
@Mel Pouvez-vous vérifier les messages que je vous ai envoyés sur Slack
samthecodingman
0

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.

Mel
la source