¿Cómo se obtienen los detalles de la base de datos de usuario relacionados con un usuario de autenticación en Firestore?

10

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:

ingrese la descripción de la imagen aquí

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);
      };
Mel
fuente
Para su información, su terminología no es del todo correcta y hace que esta pregunta sea difícil de leer. No hay nada en Firebase llamado "tabla". En Firebase Auth, solo hay usuarios: no hay "Tabla de autenticación". En Firestore, hay colecciones y documentos dentro de esas colecciones, pero no hay tablas. Estoy tratando de averiguar dónde te quedas atascado y cómo el código que has mostrado no funciona de la manera esperada, pero no lo estoy armando. Considere editar la pregunta para usar una terminología más estándar que la que encontrará en los documentos, y tener más claro lo que no funciona como espera.
Doug Stevenson
Bien: feliz de sustituir tablas por colecciones. El punto sigue siendo el mismo.
Mel
Mi punto principal es que realmente no pude entender tu punto, y la terminología no ayudó. ¿Podría explicar con más detalle lo que no funciona en el código que mostró? ¿Algo no funcionó como se esperaba? ¿Algún error o registro de depuración para ilustrar?
Doug Stevenson
Nada funciona. Espero encontrar una manera de acceder a los detalles de la colección de usuarios desde el oyente authUser. authUser se define en el controlador de contexto y el método de clase relacionado que escucha los cambios en el método. No puedo superar los atributos de la colección de autenticación: estoy tratando de obtener acceso a la colección de usuarios relacionada en firestore. No hay registros. Solo mensajes de error que dicen que el campo no está definido.
Mel
1
Sugiero comenzar con una tarea simple, hacer que funcione para ganar algo de experiencia, luego aplicarla a problemas más complejos como el que tienes ahora. No hay nada fundamentalmente incorrecto en la documentación (lo sé, porque lo uso todo el tiempo y ayudo a las personas con lo mismo). Para obtener ayuda sobre Stack Overflow, deberá ilustrar un problema específico, idealmente un MCVE que cualquiera pueda usar para reproducir el problema. Solo decir "No puedo hacer que funcione" no es suficiente. stackoverflow.com/help/minimal-reproducible-example
Doug Stevenson

Respuestas:

5

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 usuarios usersy 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):

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();
      }
    });

Entonces, dentro del observador establecido a través del onAuthStateChanged()método, cuando detectamos que el usuario ha iniciado sesión (es decir, en if (authUser) {}), usamos su uidpara consultar el documento único correspondiente a este usuario en la userscolección (ver leer un documento y el documento del get()método).

Renaud Tarnec
fuente
Entonces, ¿hay algo mal con la forma en que he definido enAuthUserListener? Entonces, si pruebo sus enmiendas a ese método, ¿qué se supone que debo hacer para llegar a la colección de usuarios de authUser?
Mel
"Entonces, ¿hay algo mal con la forma en que he definido onAuthUserListener?" -> no por lo que puedo ver. "¿Qué se supone que debo hacer para llegar a la colección de usuarios de authUser?" -> Si entiendo correctamente, desea obtener UN documento, no la colección. El código en la respuesta debería funcionar.
Renaud Tarnec
Quiero obtener authUser: ¿es su código una mejora en mi intento? No puedo encontrar una manera que funcione para que el authUser me dé acceso a la colección de usuarios que tiene el mismo uid. Estoy tratando de entender su sugerencia de código, como cómo eso mejora el mío como primer paso. ¿Puede identificar qué parte de esto es la mejora / corrección? Gracias
Mel
¿Qué pasa si lo haces this.auth.onAuthStateChanged(authUser => { if (authUser) {console.log(authUser.uid) })?
Renaud Tarnec
Puedo generar todas las propiedades de authUser (los datos de recopilación de autenticación). No puedo acceder a los datos del usuario del documento relacionado en la colección de usuarios que tiene el uid como id
Mel
1

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 (como cannot 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á dentro then()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 del then()alcance del controlador Promise . Podemos hacer esto usandowindow.setTimeout(function) .

Entonces, en su código, reemplazaría

next(authUser)

con

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

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:

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

Código editado

La explicación anterior debería permitirle corregir su código, pero aquí está mi versión de su Firebaseclase con varios cambios de facilidad de uso.

Uso:

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
}

Definición de clase:

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

Tenga en cuenta que en esta versión, el onUserDataListenermétodo devuelve la función de cancelación de suscripción onAuthStateChanged. 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.

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
fuente
¡Gracias! Lo intentaré esta noche. Estoy emocionado de aprender sobre esto de cualquier manera. Volveré en breve con comentarios.
Mel
Hola Sam, gracias por ofrecer esta sugerencia. Me tomé un tiempo para leer la documentación que vinculó y he tenido algunos pasos en esto. Si bien estoy agradecido por la ayuda, esto no resolvió mi problema. Cuando intento acceder a los atributos de la colección de usuarios, sigo recibiendo un error que dice: TypeError: No se puede leer la propiedad 'user' de undefined
Mel
@Mel Cuando ejecutó el código original, ¿se le notificó el TypeError? Esta es la primera vez que lo mencionas. Lo que significa que este código hizo su trabajo al arrojar el error fuera del alcance de la Promesa. ¿Puede proporcionar la salida de console.log(snapshot.data())?
samthecodingman
Intenté esto: el mensaje de error dice: TypeError: snapshot.data no es una función
Mel
Seguiré tratando de moverlo, tal vez no estoy tratando de registrar esto en un buen lugar
Mel
0

En su AuthContext.Providerimplementación, accede onAuthStateChanged directamente al oyente del SDK :

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

Esto debe cambiarse para usar el onAuthUserListenerde su clase auxiliar:

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

Con respecto a los mensajes de registro llenos de muchas propiedades aleatorias, esto se debe a que el firebase.Userobjeto 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:

// 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
fuente
Gracias @samthecodingman por seguir intentando ayudarme. He echado un vistazo a esto. Mi objetivo es leer el uid del authUser para que pueda usarlo para obtener los atributos de la colección de usuarios relacionada para ese usuario (el nombre en la colección de usuarios tiene más de 0 en la colección de autenticación de Firebase, así que no estoy tratando de leer las propiedades de la tabla de autenticación
Mel
El cambio sugerido a la función de componente ComponenteDidMount no arrojó un error, pero no funcionó. Cuando intenta registrar el valor de authUser en el componente del panel utilizando este detector, aparece un error que dice que authUser no está definido. No obtengo este error cuando uso el componentDidMount para AuthContext.Provider en la forma en que lo definí. Gracias por la información sobre las propiedades aleatorias en los mensajes de registro.
Mel
@Mel ¿Puedes confirmar que la última línea de tu Dashboardarchivo es export default withAuthentication(Dashboard);(y no withFirebase)
samthecodingman
Confirmado. withFirebase está incorporado en withAuthentication, por lo que se recoge a través de ese HOC.
Mel
@Mel ¿Puedes ver los mensajes que te envié en Slack?
samthecodingman
0

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.

Mel
fuente