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