Как вы получаете подробные сведения о базе данных пользователя, связанные с authUser в Firestore?

10

Я пытаюсь выяснить, как получить имя пользователя, которое является атрибутом, хранящимся в коллекции пользователей, которая была объединена с атрибутами, созданными моделью аутентификации firebase.

Я могу получить доступ к authUser - который дает мне ограниченные поля, которые firebase собирает в инструменте аутентификации, и затем я пытаюсь перейти оттуда к соответствующей коллекции пользователей (которая использует тот же uid).

У меня есть контекстный потребитель с:

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

Тогда в моем компоненте я пытаюсь использовать:

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

В моем firebase.js - я думаю, что я попытался объединить атрибуты authUser из модели Authentication с атрибутами пользовательской коллекции следующим образом:

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

Я не могу найти способ получить от authUser (который работает, чтобы получить меня к атрибутам аутентификации) - к коллекции пользователей, которая имеет идентификатор с таким же идентификатором uid из коллекции аутентификации.

Я видел этот пост , в котором, похоже, возникла та же проблема, и пытался выяснить, на что намекает ответ, - но я не могу найти способ, который работает, чтобы попасть из коллекции Authentication в коллекцию пользователей и я не знаю, что делает для меня слияние, если оно не дает мне доступ к атрибутам в коллекции пользователей из authUser.

Я пытался использовать помощник в моем firebase.js, чтобы дать мне пользователя от uid - но это тоже не помогает.

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

Следующая попытка

Чтобы добавить больше фона, я сделал тестовый компонент, который может регистрировать (но не отображать) authUser следующим образом:

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;

В журнале отображаются подробные сведения о uid, электронной почте и т. Д. В журналах, но он находится среди длинного списка элементов, многие из которых начинаются с 1 или 2 букв (я не могу найти ключ, чтобы узнать, какой у каждого из этих префиксов буквы означают). Пример извлечен ниже:

введите описание изображения здесь

ОБНОВЛЕНИЕ НА ЭТОМ КОММЕНТАРИИ:

Ранее я говорил: поля для uid, email и т. Д., Похоже, не вложены в эти префиксы, но если я попытаюсь:

 console.log(authUser.email)

Я получаю сообщение об ошибке:

TypeError: Невозможно прочитать свойство 'email' со значением NULL

Обновление: я только что понял, что в журнале консоли мне нужно развернуть выпадающее меню с надписью:

Q {I: Array (0), l:

чтобы увидеть атрибут электронной почты. Кто-нибудь знает, на что намекает этот бред? Я не могу найти ключ, чтобы выяснить, что означает Q, I или l, чтобы узнать, должен ли я ссылаться на эти вещи, чтобы добраться до соответствующих атрибутов в таблице Authentication. Может быть, если я смогу это выяснить - я найду способ добраться до коллекции пользователей, используя uid из коллекции Authentication.

Кто-нибудь использовал ответ на интерфейсе, с потребителем контекста, чтобы узнать, кто является текущим пользователем? Если да, то как вы получаете доступ к их атрибутам в модели аутентификации и как вы обращались к атрибутам в соответствующей коллекции пользователей (где docId в документе пользователя - это идентификатор пользователя из таблицы аутентификации)?

СЛЕДУЮЩАЯ ПОПЫТКА

Следующая попытка дала очень странный результат.

У меня есть 2 отдельные страницы, которые являются потребителями контекста. Разница между ними заключается в том, что один является функцией, а другой - компонентом класса.

В компоненте функции я могу сделать {authUser.email}. Когда я пытаюсь сделать то же самое в компоненте класса, я получаю сообщение об ошибке:

TypeError: Невозможно прочитать свойство 'email' со значением NULL

Эта ошибка происходит из того же сеанса с тем же вошедшим в систему пользователем.

Примечание: хотя в документации по firebase говорится, что свойство currentUser доступно для auth, я не смог заставить его работать вообще.

Мой функциональный компонент имеет:

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;

Хотя я не могу добраться до атрибутов коллекции User, где docId в документе пользователя совпадает с uid аутентифицированного пользователя, из этого компонента я могу вывести атрибут email в коллекции auth для этого пользователя.

Пока документация Firebase предоставляет этот совет для управления пользователями и доступа к атрибутам, я не нашел способа реализовать этот подход в реакции. Каждый вариант попытки сделать это, как с помощью помощников в моем firebase.js, так и с попытки начать с нуля в компоненте, приводит к ошибкам доступа к firebase. Тем не менее, я могу создать список пользователей и связанную с ними информацию о коллекции пользователей (я не могу получить пользователя на основе того, кто является authUser).

Мой компонент класса имеет:

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;

В моем AuthContext.Provider - у меня есть:

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;

СЛЕДУЮЩАЯ ПОПЫТКА

Действительно странно, что с этой попыткой я пытаюсь сохранить в журнале значения, которые, как я вижу, существуют в базе данных, и значение name возвращается как «неопределенное», где в базе данных есть строка.

Эта попытка имеет:

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

СЛЕДУЮЩАЯ ПОПЫТКА

Так что эта попытка неуклюжа и не использует хелперы или запросы моментальных снимков, которые я пытался использовать выше, но регистрирует атрибуты документа пользовательской коллекции на консоли следующим образом:

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

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

    } 

Что я не могу сделать, так это найти способ визуализации этого имени в jsx

Как вы на самом деле печатать вывод?

Когда я пытаюсь:

{ 


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

                }

Я получаю сообщение об ошибке:

Ошибка типа: this.props.firebase.db.collection (...). Doc (...). Get (...). Data не является функцией

Когда я пытаюсь:

{ 



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

Я получаю сообщение об ошибке:

Строка 281: 23: ожидал присваивания или вызова функции и вместо этого увидел выражение no-unused-expression

Когда я пытаюсь:

{ 


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

Сообщение об ошибке гласит:

Ожидал присваивания или вызова функции и вместо этого увидел выражение

Я готов отказаться от попыток выяснить, как заставить работать запросы моментальных снимков - если я могу просто получить имя пользовательской коллекции для отображения на экране. Кто-нибудь может помочь с этим шагом?

СЛЕДУЮЩАЯ ПОПЫТКА

Я нашел этот пост . В нем есть хорошее объяснение того, что должно произойти, но я не могу реализовать это, как показано, потому что componentDidMount не знает, что такое authUser.

Моя текущая попытка заключается в следующем - однако, как написано в настоящее время, authUser является оберткой возвращаемого значения - и сегмент componentDidMount не знает, что такое 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);

СЛЕДУЮЩАЯ ПОПЫТКА

Затем я попытался обернуть маршрут для панели мониторинга внутри AuthContext.Consumer, чтобы его мог использовать весь компонент, что позволило мне получить доступ к зарегистрированному пользователю в функции componentDidMount.

Я изменил маршрут на:

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

и удалил потребителя из оператора рендеринга компонента панели мониторинга.

Затем в componentDidMount на компоненте Dashboard я попытался:

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

Когда я пытаюсь это сделать, я получаю сообщение об ошибке:

FirebaseError: Функция CollectionReference.doc () требует, чтобы ее первый аргумент имел тип непустой строки, но это был: пользовательский объект DocumentReference

СЛЕДУЮЩАЯ ПОПЫТКА Представленные ниже люди, кажется, находят что-то полезное в первом предложенном решении. Я не смог найти в нем ничего полезного, но, читая его предложения, я изо всех сил пытаюсь понять, как работает пример в документации по Firebase (он не раскрывает, как дать значение: uid для запроса .doc (). ), который выглядит следующим образом:

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

принципиально отличается от моей попытки в функции componentDidMount, которая:

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

    );  
  }

Возможно, решение этого шага - ключ, который поможет приблизить это к результату. Может ли кто-нибудь найти лучшую документацию по пожарному хранилищу, чтобы показать, как получить запись коллекции пользователей, используя зарегистрированный пользовательский uid слушателя?

С этой целью я вижу из примера лаборатории кода FriendlyEats , что делается попытка присвоить doc.id значение поиска id в коде. Я не знаю, на каком языке написан этот код - но он действительно похож на то, что я пытаюсь сделать - я просто не понимаю, как перейти от этого примера к тому, с чем я знаю, как работать.

display: function(doc) {
      var data = doc.data();
      data['.id'] = doc.id;
      data['go_to_restaurant'] = function() {
        that.router.navigate('/restaurants/' + doc.id);
      };
Мел
источник
К вашему сведению, ваша терминология не совсем верна, и затрудняет чтение этого вопроса. В Firebase нет ничего, что называется "столом". В Firebase Auth есть только пользователи - нет таблицы аутентификации. В Firestore есть коллекции и документы в этих коллекциях, но нет таблиц. Я пытаюсь выяснить, где вы застряли и как код, который вы показали, работает не так, как вы ожидаете, но я просто не собираю это воедино. Подумайте о том, чтобы отредактировать вопрос, чтобы использовать более стандартную терминологию, которую вы найдете в документах, и получить более четкое представление о том, что не работает так, как вы ожидаете.
Даг Стивенсон
Прекрасно - с удовольствием подставим столы для коллекций. Дело в том же.
Мел
Главное, что я не смог понять вашу точку зрения, и терминология не помогла. Не могли бы вы объяснить более подробно, что не работает в коде, который вы показали? Что-то не работает, как ожидалось? Любые ошибки или журналы отладки для иллюстрации?
Даг Стивенсон
Ничего не работает Я ожидаю найти способ доступа к деталям коллекции пользователей из слушателя authUser. authUser определен в обработчике контекста и связанном методе класса, который прослушивает изменения в методе. Я не могу пройти мимо атрибутов в коллекции аутентификации - я пытаюсь получить доступ к соответствующей коллекции пользователей в firestore. Нет логов. Просто сообщения об ошибках, которые говорят, что поле не определено.
Мел
1
Я предлагаю начать с простого задания, заставить его работать, чтобы получить некоторый опыт, а затем применить его к более сложным проблемам, таким как та, что у вас есть сейчас. В документации нет ничего принципиально неправильного (я знаю, потому что я все время ее использую и помогаю людям с этим). Чтобы получить помощь по переполнению стека, вам нужно проиллюстрировать конкретную проблему, в идеале MCVE, которую каждый может использовать для воспроизведения проблемы. Просто сказать «я не могу заставить его работать» недостаточно. stackoverflow.com/help/minimal-reproducible-example
Дуг Стивенсон

Ответы:

5

Из последней строки вашего вопроса ( users = () => this.db.collection('users');) я понимаю, что вызывается коллекция, в которой вы храните дополнительную информацию о пользователях, usersи что пользовательский документ в этой коллекции использует userId (uid) в качестве docId.

Следующее должно сделать трюк (не проверено):

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

Таким образом, в наблюдателе, установленном с помощью onAuthStateChanged()метода, когда мы обнаруживаем, что пользователь вошел в систему (т.е. в if (authUser) {}), мы используем его uidдля запроса уникального документа, соответствующего этому пользователю в usersколлекции (см. Чтение одного документа и документ документа get()метод).

Рено Тарнек
источник
Так что-то не так с тем, как я определил onAuthUserListener? Тогда, если я попробую ваши поправки к этому методу, что я должен сделать, чтобы получить коллекцию пользователей от authUser?
Мел
«Так что-то не так с тем, как я определил onAuthUserListener?» -> не из того, что я вижу. "что я должен сделать, чтобы получить доступ к коллекции пользователей из authUser?" -> Если я правильно понимаю, вы хотите получить один документ, а не коллекцию. Код в ответе должен работать.
Рено Тарнек
Я хочу получить authUser - ваш код - улучшение моей попытки? Я не могу найти способ, с помощью которого authUser дает мне доступ к коллекции пользователей с таким же идентификатором пользователя. Я пытаюсь понять ваше предложение кода - как это улучшить мой первый шаг. Пожалуйста, можете ли вы определить, какая часть этого улучшения / исправления? Спасибо
Мел
Что произойдет, если вы делаете this.auth.onAuthStateChanged(authUser => { if (authUser) {console.log(authUser.uid) })?
Рено Тарнек
Я могу вывести все свойства authUser (данные коллекции аутентификации). Я не могу получить пользовательские данные из соответствующего документа в пользовательской коллекции, в которой в качестве идентификатора пользователя указан uid
Мел
1

У меня есть теория, которую я бы хотел проверить.

Я думаю, что когда вы вызываете next(authUser)внутри вашего onAuthStateChangedобработчика, во время его выполнения он сталкивается с ошибкой (например, cannot read property 'name' of undefined at ...).

Причина, по которой ваш код не работает должным образом, заключается в том, что там, где вы звоните next(authUser), он находится внутри then()цепочки Promise. Любые ошибки, возникшие в Обещании, будут обнаружены и приведут к отклонению Обещания. Когда Promise отклоняется, он вызывает вызывающие ошибки обработчики ошибок. В рассматриваемой цепочке Promise такого обработчика ошибок нет.

Если я потерял вас, прочитайте это сообщение в блоге для ускоренного курса Promises, а затем возвращайтесь.

Итак, что мы можем сделать, чтобы избежать такой ситуации? Простейшим будет вызов next(authUser) за пределами области действия then()обработчика Promise . Мы можем сделать это, используя window.setTimeout(function).

Так что в вашем коде вы бы заменили

next(authUser)

с

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

Это приведет к возникновению любых ошибок, как обычно, а не к цепочке обещаний.

Важно отметить, что у вас нет обработчика catch, который обрабатывает userDocRef.get()ошибки. Так что просто добавьте .catch(() => setTimeout(fallback))в конец then()так, чтобы ваш код использовал резервный метод, если он выдает ошибку.

Итак, мы заканчиваем с:

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

Отредактированный код

Вышеприведенное объяснение должно позволить вам исправить ваш код, но вот моя версия вашего Firebaseкласса с различными изменениями простоты использования.

Применение:

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
}

Определение класса:

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

Обратите внимание, что в этой версии onUserDataListenerметод возвращает функцию отказа от подписки onAuthStateChanged. Когда ваш компонент размонтирован, вы должны отсоединить все соответствующие прослушиватели, чтобы не было утечки памяти или неработающего кода, работающего в фоновом режиме, когда он не нужен.

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
источник
Спасибо! Я попробую это сегодня вечером. Я взволнован, чтобы узнать об этом в любом случае. Я скоро вернусь с обратной связью.
Мел
Привет Сэм - спасибо за предложение. Я потратил некоторое время, чтобы прочитать документацию, которую вы связали, и у меня было несколько попыток в этом. Хотя я благодарен за помощь - это не решило мою проблему. Когда я пытаюсь получить доступ к атрибутам коллекции пользователей, я все еще получаю сообщение об ошибке: TypeError: Невозможно прочитать свойство 'user' из undefined
Мел
@Mel Когда вы запустили оригинальный код, вы были уведомлены об ошибке TypeError? Это первый раз, когда вы упомянули об этом. Это означает, что этот код выполнил свою работу, выбрасывая ошибку за пределы Обещания. Можете ли вы предоставить вывод console.log(snapshot.data())?
samthecodingman
Я попытался это - сообщение об ошибке говорит: TypeError: snapshot.data не является функцией
Мел
Я буду продолжать пытаться переместить это - возможно, я не пытаюсь записать это в хорошем месте
Мел
0

В вашей AuthContext.Providerреализации вы получаете доступ к onAuthStateChanged слушателю SDK напрямую:

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

Это должно быть изменено для использования onAuthUserListenerиз вашего вспомогательного класса:

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

Что касается сообщений журнала, заполненных множеством случайных свойств, это связано с тем, что firebase.Userобъект имеет как открытый API, так и реализацию с рядом частных свойств и методов, которые минимизируются при компиляции. Поскольку эти минимизированные свойства и методы явно не помечены как не перечисляемые, они включаются в любой вывод журнала. Если вместо этого вы хотите регистрировать только те части, которые действительно полезны, вы можете деструктурировать и реструктурировать объект, используя:

// 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
источник
Спасибо @samthecodingman за продолжение попытки помочь мне. Я посмотрел на это. Моя цель - прочитать uid authUser, чтобы я мог использовать его для получения атрибутов соответствующей коллекции пользователей для этого пользователя (имя в коллекции пользователей имеет больше, чем displayName в коллекции аутентификации firebase - поэтому я не пытаюсь читать свойства таблицы аутентификации
Мел
Предлагаемое изменение функции слушателя componentDidMount не выдало ошибку - но это не сработало. Когда он пытается записать значение authUser в компонент панели мониторинга, используя этот слушатель - я получаю сообщение об ошибке, говоря, что authUser не определен. Я не получаю эту ошибку, когда использую componentDidMount для AuthContext.Provider так, как я его определил. Спасибо за информацию о случайных свойствах в журнале сообщений.
Мел
@Mel Можете ли вы подтвердить, что последняя строка вашего Dashboardфайла export default withAuthentication(Dashboard);(а не withFirebase)
samthecodingman
Подтверждено. withFirebase включен в withAuthentication - таким образом, это поднято через тот HOC.
Мел
@Mel Можете ли вы проверить сообщения, которые я отправил вам в Slack
samthecodingman
0

Если кто-то еще застрял подобным образом, я нашел решение здесь: Firebase & React: CollectionReference.doc () тип аргумента

Он не работает при обновлении страницы (по-прежнему выдает ошибку, что uid имеет значение null), но реагирует на перехваты для использования useEffect, если функция componentDidMount заменена комбинацией Mount и Update. Я пытаюсь это дальше.

Мел
источник