web-dev-qa-db-fra.com

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

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'aller de là à la collection d'utilisateurs associée (qui utilise le même uid).

J'ai un consommateur de contexte de réaction avec:

import React from 'react';
const AuthUserContext = React.createContext(null);
export default AuthUserContext;

Ensuite, dans mon composant, j'essaye 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 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 m'amener aux attributs d'authentification) - à la collection d'utilisateurs qui a un identifiant avec le même uid de la collection d'authentification.

J'ai vu this post , qui semble avoir le même problème et j'ai essayé de déterminer ce que la réponse est censée faire allusion - mais je n'arrive pas à trouver un moyen qui fonctionne pour obtenir de la collection Authentication à 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 à partir d'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 non plus aider.

user = uid => this.db.doc(`users/${uid}`);
  users = () => this.db.collection('users');

Prochaine tentative

Pour ajouter plus d'arrière-plan, j'ai créé un composant de test qui peut enregistrer (mais pas rendre) 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écédés de 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:

enter image description here

MISE À JOUR SUR CE COMMENTAIRE:

Auparavant, j'ai dit: 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é 'email' 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: Array (0), l:

pour voir l'attribut email. Est-ce que quelqu'un sait à quoi ce jibberish 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 Authentication.

Quelqu'un at-il utilisé réagir sur le front-end, avec un consommateur de contexte pour savoir qui est l'utilisateur actuel? Si tel est le cas, 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 Authentication)?

TENTATIVE SUIVANTE

La prochaine tentative a produit un résultat très étrange.

J'ai 2 pages séparées 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 de fonction, je peux rendre {authUser.email}. Lorsque j'essaye de faire la même chose dans le composant de classe, j'obtiens une erreur qui dit:

TypeError: impossible de lire la propriété 'email' de null

Cette erreur provient de la même session avec le même utilisateur connecté.

Remarque: alors que la documentation de Firebase indique qu'une propriété currentUser est disponible sur auth - je n'ai pas du tout pu faire fonctionner cela.

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 générer l'attribut email sur la collection auth pour cet utilisateur.

Alors que documentation Firebase fournit ces conseils pour gérer les utilisateurs et accéder aux attributs ici, je n'ai pas trouvé de moyen d'implémenter cette approche dans react. Chaque variante d'une tentative de faire cela, à la fois en créant des helpers dans mon firebase.js et en essayant de repartir de zéro dans un composant produit des erreurs d'accès à firebase. Je peux cependant produire une liste d'utilisateurs et leurs informations relatives à la collection d'utilisateurs (je ne peux pas obtenir un utilisateur basé sur l'identité de l'utilisateur 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;

TENTATIVE SUIVANTE

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 que la valeur du nom soit 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);

TENTATIVE SUIVANTE

Cette tentative est donc maladroite et n'utilise pas les helpers ou les requêtes d'instantané que j'ai essayé d'utiliser ci-dessus, mais enregistrez les attributs du document de collection utilisateur dans 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

                }

J'obtiens 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>
              })
            } 

J'obtiens une erreur qui dit:

Ligne 281: 23: Attendu un appel d'affectation ou de fonction et à la place vu une expression no-unused-expressions

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 a plutôt vu une expression

Je suis prêt à renoncer à essayer de savoir comment faire fonctionner les requêtes instantanées - si je peux simplement obtenir le nom de la collection d'utilisateurs à afficher à l'écran. Quelqu'un peut-il aider à cette étape?

TENTATIVE SUIVANTE

J'ai trouvé ce post . Il a une bonne explication de ce qui doit se passer, mais je ne peux pas l'implémenter comme indiqué car componentDidMount ne sait pas ce qu'est authUser.

Ma tentative actuelle est la suivante - cependant, comme actuellement écrit, 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);

TENTATIVE SUIVANTE

Ensuite, j'ai essayé d'encapsuler l'itinéraire pour le 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 en:

<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.

Ensuite, 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,
      });  
  }                  

Quand j'essaye ceci, j'obtiens une erreur qui dit:

FirebaseError: Function CollectionReference.doc () nécessite que son premier argument soit de type chaîne non vide, mais c'était: 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 pu y trouver d'utile, mais en relisant ses suggestions, j'ai du mal à voir comment l'exemple de la documentation de firebase (il ne révèle pas comment donner une valeur: uid à la requête .doc () ), qui se présente comme suit:

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 résoudre 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 le laboratoire de code FriendlyEats exemple , 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);
      };
10
Mel

Si quelqu'un d'autre est également bloqué, 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 doivent remplacer la fonction componentDidMount par une combinaison de Mount et Update. J'essaye ça ensuite.

0
Mel

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

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

Cela devrait être changé pour utiliser le onAuthUserListener de 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, c'est parce que le firebase.User objet a à la fois un API publique et un implémentation avec un certain nombre de propriétés privées et de méthodes 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 à la place enregistrer uniquement les parties réellement utiles, vous pouvez déstructurer 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));
0
samthecodingman