Como você obtém os detalhes do banco de dados do usuário relacionados a um authUser no Firestore?

10

Estou tentando descobrir como obter um nome de usuário que é um atributo armazenado em uma coleção de usuários, que foi mesclada com os atributos criados pelo modelo de autenticação do firebase.

Posso acessar o authUser - que fornece os campos limitados que o firebase coleta na ferramenta de autenticação e, em seguida, estou tentando ir de lá para a coleção de usuários relacionada (que usa o mesmo uid).

Eu tenho um consumidor de contexto de reação com:

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

Então, no meu componente, estou tentando 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);

No meu firebase.js - acho que tentei mesclar os atributos authUser do modelo de autenticação com os atributos de coleção de usuários da seguinte maneira:

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

Não consigo encontrar uma maneira de obter a partir do authUser (que funciona para obter os atributos de autenticação) - para a coleção de usuários que possui um ID com o mesmo uid da coleção de autenticação.

Eu vi esse post , que parece ter o mesmo problema e tentei descobrir o que a resposta deveria estar sugerindo - mas não consigo encontrar uma maneira que funcione para passar da coleção de autenticação para a coleção de usuários e não sei o que a mesclagem está fazendo por mim, se não estiver me dando acesso aos atributos na coleção de usuários do authUser.

Tentei usar um auxiliar no meu firebase.js para fornecer um usuário de um uid - mas isso também não parece ajudar.

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

Próxima tentativa

Para adicionar mais informações, criei um componente de teste que pode registrar (mas não renderizar) o authUser, da seguinte maneira:

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;

O log mostra os detalhes de uid, email etc. nos logs, mas está entre uma longa lista de itens - muitos dos quais são precedidos por 1 ou 2 letras (não consigo encontrar uma chave para descobrir qual é o prefixo) letras significam). Exemplo extraído abaixo:

insira a descrição da imagem aqui

ATUALIZAÇÃO NESTE COMENTÁRIO:

Anteriormente, eu disse: Os campos para uid, email etc não parecem estar aninhados abaixo desses prefixos, mas se eu tentar:

 console.log(authUser.email)

, Recebo um erro que diz:

TypeError: Não é possível ler a propriedade 'email' de null

A atualização: acabei de perceber que, no log do console, tenho que expandir um menu suspenso chamado:

Q {I: Matriz (0), l:

para ver o atributo de email. Alguém sabe o que essa bobagem alude? Não consigo encontrar uma chave para descobrir o que Q, I ou l significa, para saber se devo fazer referência a essas coisas para obter os atributos relevantes na tabela Autenticação. Talvez se eu puder descobrir isso - eu posso encontrar uma maneira de acessar a coleção de usuários usando o uid da coleção Authentication.

Alguém já usou reagir no front end, com um consumidor de contexto para descobrir quem é o usuário atual? Em caso afirmativo, como você acessa seus atributos no modelo de autenticação e como você acessou os atributos na coleção de usuários relacionada (onde o docId no documento de usuário é o uid da tabela de autenticação)?

PRÓXIMA TENTATIVA

A próxima tentativa produziu um resultado muito estranho.

Eu tenho 2 páginas separadas que são consumidores de contexto. A diferença entre eles é que um é uma função e o outro é um componente de classe.

No componente de função, eu posso renderizar {authUser.email}. Quando tento fazer a mesma coisa no componente de classe, recebo um erro que diz:

TypeError: Não é possível ler a propriedade 'email' de null

Este erro é proveniente da mesma sessão com o mesmo usuário conectado.

Nota: enquanto a documentação do firebase diz que uma propriedade currentUser está disponível no auth - eu não consegui fazer isso funcionar.

Meu componente de função possui:

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;

Embora não seja possível acessar os atributos da coleção de usuários em que o docId no documento do usuário é o mesmo que o uid do usuário autenticado, a partir deste componente, posso gerar o atributo de email na coleção de autenticação para esse usuário.

Embora a documentação do Firebase forneça esse conselho para gerenciar usuários e acessar atributos aqui, não encontrei uma maneira de implementar essa abordagem ao reagir. Todas as variações de uma tentativa de fazer isso, criando auxiliares no meu firebase.js e tentando iniciar do zero em um componente, produz erros no acesso ao firebase. No entanto, posso produzir uma lista de usuários e suas informações relacionadas à coleção de usuários (não consigo obter um usuário com base em quem é o authUser).

Meu componente de classe possui:

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;

No meu AuthContext.Provider - eu tenho:

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ÓXIMA TENTATIVA

É realmente estranho que, com essa tentativa, eu estou tentando consolar o registro dos valores que posso ver no banco de dados e o valor do nome esteja sendo retornado como 'indefinido', onde o banco de dados possui uma string.

Esta tentativa tem:

    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ÓXIMA TENTATIVA

Portanto, essa tentativa é desajeitada e não faz uso dos auxiliares ou das consultas de instantâneo que tentei usar acima, mas registre os atributos do documento de coleção do usuário no console da seguinte maneira:

{this.props.firebase.db.collection ('users'). doc (authUser.uid) .get ()

      .then(doc => {
          console.log(doc.data().name) 
      })

    } 

O que não posso fazer é encontrar uma maneira de renderizar esse nome no jsx

Como você realmente imprime a saída?

Quando tento:

{ 


this.props.firebase.db.collection('users').doc(authUser.uid).get().data().name

                }

Eu recebo um erro que diz:

TypeError: this.props.firebase.db.collection (...). Doc (...). Get (...). Data não é uma função

Quando tento:

{ 



this.props.firebase.db.collection('users').doc(authUser.uid).get()
              .then(doc => {
                  console.log(doc.data().name), 
                  <p>doc.data().name</p>
              })
            } 

Eu recebo um erro que diz:

Linha 281: 23: esperava uma atribuição ou chamada de função e, em vez disso, viu uma expressão no-unused-expression

Quando tento:

{ 


this.props.firebase.db.collection('users').doc(authUser.uid).get("name")
              .then(doc => {
                  console.log(doc.data().name), 
                  <p>doc.data().name</p>
              })
            }

A mensagem de erro diz:

Esperava uma atribuição ou chamada de função e viu uma expressão

Estou pronto para desistir de tentar descobrir como fazer com que as consultas de snapshot funcionem - se eu puder obter o nome da coleção de usuários para renderizar na tela. Alguém pode ajudar com essa etapa?

PRÓXIMA TENTATIVA

Encontrei este post . Ele tem uma boa explicação do que precisa acontecer, mas não posso implementá-lo como mostrado, pois componentDidMount não sabe o que é o authUser.

Minha tentativa atual é a seguinte - no entanto, conforme atualmente escrito, authUser é um invólucro no valor de retorno - e o segmento componentDidMount não sabe o que é 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ÓXIMA TENTATIVA

Em seguida, tentei agrupar a rota para o painel dentro do AuthContext.Consumer para que todo o componente pudesse usá-lo - permitindo acessar o usuário conectado na função componentDidMount.

Alterei a rota para:

<Route path={ROUTES.DASHBOARD} render={props => (
          <AuthUserContext.Consumer>
             { authUser => ( 
                <Dashboard authUser={authUser} {...props} />  
             )}
          </AuthUserContext.Consumer>
        )} />

e removeu o consumidor da instrução de renderização do componente do painel.

Em seguida, no componentDidMount no componente Dashboard, tentei:

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

Quando tento isso, recebo um erro que diz:

FirebaseError: A função CollectionReference.doc () requer que seu primeiro argumento seja do tipo string não vazia, mas era: um objeto DocumentReference personalizado

PRÓXIMA TENTATIVA As pessoas abaixo parecem encontrar algo útil na primeira solução proposta. Não consegui encontrar nada útil nele, mas, lendo as sugestões, estou lutando para ver como o exemplo na documentação do firebase (não revela como atribuir um valor: uid à solicitação .doc () ), que é o seguinte:

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!");
      }

é fundamentalmente diferente da minha tentativa na função componentDidMount, que é:

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

    );  
  }

Talvez resolver esse passo seja uma pista que ajude a levar isso em direção a um resultado. Alguém pode encontrar uma documentação melhor do firestore para mostrar como obter um registro de coleção de usuários usando um uid de ouvinte de usuário conectado?

Para esse fim, posso ver no exemplo do laboratório de códigos FriendlyEats , que há uma tentativa de fornecer um doc.id ao valor da pesquisa de identificação no código. Não sei em que idioma esse código está escrito - mas é semelhante ao que estou tentando fazer - simplesmente não consigo ver como passar desse exemplo para algo com o qual sei trabalhar.

display: function(doc) {
      var data = doc.data();
      data['.id'] = doc.id;
      data['go_to_restaurant'] = function() {
        that.router.navigate('/restaurants/' + doc.id);
      };
Mel
fonte
Para sua informação, sua terminologia não está correta e dificulta a leitura dessa pergunta. Não há nada no Firebase chamado "tabela". No Firebase Auth, existem apenas usuários - não há "Tabela de autenticação". No Firestore, existem coleções e documentos dentro dessas coleções, mas nenhuma tabela. Estou tentando descobrir onde você está ficando paralisado e como o código que você mostrou não funciona da maneira que você espera, mas simplesmente não estou reunindo. Considere editar a pergunta para usar uma terminologia mais padrão do que você encontrará nos documentos e seja mais claro sobre o que não está funcionando conforme o esperado.
Doug Stevenson
Bem - prazer em substituir tabelas por coleções. O ponto ainda é o mesmo.
Mel
Meu ponto principal é que eu realmente não entendi seu ponto de vista, e a terminologia não ajudou. Você poderia explicar com mais detalhes o que não funciona no código que você mostrou? Algo não funcionou como esperado? Algum erro ou registro de depuração para ilustrar?
Doug Stevenson
Nada funciona. Espero encontrar uma maneira de acessar os detalhes da coleção de usuários no ouvinte authUser. authUser é definido no manipulador de contexto e no método de classe relacionado que escuta as alterações no método. Não consigo passar dos atributos na coleção de autenticação - estou tentando obter acesso à coleção de usuários relacionados no firestore. Sem registros. Apenas mensagens de erro que dizem que o campo está indefinido.
Mel
11
Sugiro começar com uma tarefa simples, fazer com que isso funcione para ganhar alguma experiência e depois aplicá-la a problemas mais complexos como o que você tem agora. Não há nada de fundamentalmente incorreto na documentação (eu sei, porque eu a uso o tempo todo e ajudo as pessoas com a mesma). Para obter ajuda no Stack Overflow, você precisará ilustrar um problema específico, idealmente um MCVE que qualquer pessoa possa usar para reproduzir o problema. Apenas dizer "não consigo fazer funcionar" não é suficiente. stackoverflow.com/help/minimal-reproducible-example
Doug Stevenson

Respostas:

5

Entendo, desde a última linha da sua pergunta ( users = () => this.db.collection('users');), que a coleção onde você armazena informações extras sobre os usuários é chamada userse que um documento de usuário nesta coleção usa o userId (uid) como docId.

O seguinte deve fazer o truque (não testado):

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

Portanto, dentro do observador definido pelo onAuthStateChanged()método, quando detectamos que o usuário está conectado (ou seja, dentro if (authUser) {}), usamos o uidpara consultar o documento exclusivo correspondente a esse usuário na userscoleção (consulte ler um documento e o documento do get()método).

Renaud Tarnec
fonte
Então, há algo errado com a maneira como eu defini onAuthUserListener? Então, se eu tentar suas alterações nesse método, o que devo fazer para obter a coleção de usuários de authUser?
Mel
"Então, há algo errado com a maneira que eu defini noAuthUserListener?" -> não do que eu posso ver. "o que devo fazer para acessar a coleção de usuários do authUser?" -> Se bem entendi, você deseja obter UM documento, não a coleção. O código na resposta deve funcionar.
Renaud Tarnec
Quero obter o authUser - o seu código é uma melhoria na minha tentativa? Não consigo encontrar uma maneira que funcione para que o authUser me dê acesso à coleção de usuários que possui o mesmo uid. Estou tentando entender sua sugestão de código - para saber como isso melhora o meu como um primeiro passo. Por favor, você pode identificar qual parte é a melhoria / correção? Obrigado
Mel
O que acontece se você fizer this.auth.onAuthStateChanged(authUser => { if (authUser) {console.log(authUser.uid) })?
Renaud Tarnec
Eu posso gerar todas as propriedades authUser (os dados da coleção de autenticação). Não consigo acessar os dados do usuário do documento relacionado na coleção de usuários que possui o uid como o id
Mel
1

Eu tenho uma teoria que eu gostaria que você testasse.

Eu acho que quando você está chamando next(authUser)dentro do seu onAuthStateChangedmanipulador, durante sua execução, ele encontra um erro (como cannot read property 'name' of undefined at ...).

O motivo pelo qual seu código não está funcionando conforme o esperado é que, onde você liga next(authUser), ele está dentro then()da cadeia Promise. Quaisquer erros lançados dentro de uma promessa serão capturados e farão com que a promessa seja rejeitada. Quando uma promessa é rejeitada, ela chama seus manipuladores de erro anexados ao erro. Atualmente, a cadeia Promise em questão não possui esse manipulador de erros.

Se eu te perdi, leia esta postagem do blog para obter um curso intensivo do Promises e depois volte.

Então, o que podemos fazer para evitar essa situação? O mais simples seria chamar next(authUser) forathen() do escopo do manipulador Promise . Nós podemos fazer isso usando window.setTimeout(function).

Então, no seu código, você substituiria

next(authUser)

com

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

Isso lançará quaisquer erros normalmente, em vez de serem capturados pela cadeia Promise.

Importante, você não tem um manipulador de captura que lida com userDocRef.get()falha. Portanto, basta adicionar .catch(() => setTimeout(fallback))no final do arquivo then()para que seu código use o método fallback, se ocorrer um erro.

Então, acabamos com:

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

A explicação acima deve permitir que você corrija seu código, mas aqui está a minha versão da sua Firebaseclasse com várias alterações na facilidade 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
}

Definição 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 ...
}

Observe que nesta versão, o onUserDataListenermétodo retorna a função de cancelamento de assinatura de onAuthStateChanged. Quando o seu componente estiver desmontado, você deve desconectar os ouvintes relevantes para não haver vazamento de memória ou código em execução em segundo plano quando não for necessário.

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
fonte
Obrigado! Vou tentar isso hoje à noite. Estou animado para aprender sobre isso de qualquer maneira. Voltarei em breve com feedback.
Mel
Olá Sam - obrigado por oferecer esta sugestão. Demorei um pouco para ler a documentação que você vinculou e já fiz algumas tentativas. Embora eu seja grato pela ajuda - isso não resolveu meu problema. Quando tento acessar os atributos da coleção de usuários, ainda estou recebendo um erro que diz: TypeError: Não é possível ler a propriedade 'user' de indefinido
Mel
@Mel Ao executar o código original, você foi notificado sobre o TypeError? Esta é a primeira vez que você menciona. O que significa que esse código fez o trabalho de lançar o erro fora do escopo da Promessa. Você pode fornecer a saída de console.log(snapshot.data())?
samthecodingman 15/01
Eu tentei isso - a mensagem de erro diz: TypeError: snapshot.data não é uma função
Mel
Vou continuar tentando movê-lo - talvez não esteja tentando registrar isso em um bom lugar
Mel
0

Na sua AuthContext.Providerimplementação, você acessa onAuthStateChanged diretamente o ouvinte do SDK :

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

Isso deve ser alterado para usar a onAuthUserListenerclasse de sua classe auxiliar:

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

Em relação às mensagens de log preenchidas com muitas propriedades aleatórias, isso ocorre porque o firebase.Userobjeto possui uma API pública e uma implementação com várias propriedades e métodos particulares que são minificados quando compilados. Como essas propriedades e métodos minificados não são explicitamente marcados como não enumeráveis, eles são incluídos em qualquer saída de log. Se você preferir registrar apenas as peças que são realmente úteis, poderá desestruturar e reestruturar o 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
fonte
Obrigado @samthecodingman por continuar tentando me ajudar. Eu dei uma olhada nisso. Meu objetivo é ler o uid do authUser para que eu possa usá-lo para obter atributos da coleção de usuários relacionados para esse usuário (o nome na coleção de usuários tem mais do que displayName na coleção de autenticação do firebase - por isso não estou tentando leia as propriedades da tabela de autenticação
Mel
A alteração sugerida na função listener componentDidMount não gerou um erro - mas não funcionou. Quando ele tenta registrar o valor de authUser no componente do painel usando este ouvinte - recebo um erro dizendo que authUser não está definido. Não recebo esse erro quando uso o componentDidMount para o AuthContext.Provider da maneira que o defini. Obrigado pelas informações sobre as propriedades aleatórias nas mensagens de log.
Mel
@Mel Você pode confirmar que a última linha do seu Dashboardarquivo é export default withAuthentication(Dashboard);(e não withFirebase)
samthecodingman 25/01
Confirmado. O withFirebase é incorporado no withAuthentication - para que seja captado através desse HOC.
Mel
@Mel Você pode checar as mensagens que eu te enviei no Slack
samthecodingman 25/01
0

Se alguém mais estiver preso da mesma forma, encontrei a solução aqui: tipo de argumento Firebase & React: CollectionReference.doc ()

Ele não funciona na atualização da página (ainda gera um erro de que o uid é nulo), mas os ganchos de reação para useEffect devem substituir a função componentDidMount por uma combinação de Montagem e Atualização. Estou tentando isso a seguir.

Mel
fonte