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:
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);
};
Respostas:
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 é chamadausers
e que um documento de usuário nesta coleção usa o userId (uid) como docId.O seguinte deve fazer o truque (não testado):
Portanto, dentro do observador definido pelo
onAuthStateChanged()
método, quando detectamos que o usuário está conectado (ou seja, dentroif (authUser) {}
), usamos ouid
para consultar o documento exclusivo correspondente a esse usuário nausers
coleção (consulte ler um documento e o documento doget()
método).fonte
this.auth.onAuthStateChanged(authUser => { if (authUser) {console.log(authUser.uid) })
?Eu tenho uma teoria que eu gostaria que você testasse.
Eu acho que quando você está chamando
next(authUser)
dentro do seuonAuthStateChanged
manipulador, durante sua execução, ele encontra um erro (comocannot 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á dentrothen()
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 usandowindow.setTimeout(function)
.Então, no seu código, você substituiria
com
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 arquivothen()
para que seu código use o método fallback, se ocorrer um erro.Então, acabamos com:
Código editado
A explicação acima deve permitir que você corrija seu código, mas aqui está a minha versão da sua
Firebase
classe com várias alterações na facilidade de uso.Uso:
Definição de classe:
Observe que nesta versão, o
onUserDataListener
método retorna a função de cancelamento de assinatura deonAuthStateChanged
. 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.fonte
console.log(snapshot.data())
?Na sua
AuthContext.Provider
implementação, você acessaonAuthStateChanged
diretamente o ouvinte do SDK :Isso deve ser alterado para usar a
onAuthUserListener
classe de sua classe auxiliar:Em relação às mensagens de log preenchidas com muitas propriedades aleatórias, isso ocorre porque o
firebase.User
objeto 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:fonte
Dashboard
arquivo éexport default withAuthentication(Dashboard);
(e nãowithFirebase
)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.
fonte