Як отримати детальну інформацію про користувача, пов’язану з автором користувача у 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 з моделі автентифікації з атрибутами колекції користувачів таким чином:

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 (що працює, щоб отримати мене до атрибутів Authentication) - до колекції користувачів, яка має ідентифікатор з тим самим 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, електронної пошти тощо не здаються вкладеними під цими префіксами, але якщо я спробую:

 console.log(authUser.email)

, Я отримую помилку, яка говорить:

TypeError: Неможливо прочитати властивість 'email' з null

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

Q {I: масив (0), л:

щоб побачити атрибут електронної пошти. Хтось знає, на що на це натякає цей вереск? Я не можу знайти ключ, щоб зрозуміти, що означає Q, я чи я, щоб знати, чи повинен я посилатися на ці речі, щоб потрапити на відповідні атрибути в таблиці автентифікації. Можливо, якщо я можу це зрозуміти - я можу знайти спосіб потрапити до колекції користувачів за допомогою uid з колекції Authentication.

Хтось із реагуючих реагував на передній частині, щоб споживач контексту дізнався, хто такий поточний користувач? Якщо так, то як ви отримуєте доступ до їх атрибутів на моделі автентифікації та як ви отримали доступ до атрибутів у відповідній колекції користувача (де docId у документі користувача є uid з таблиці автентифікації)?

НАСЛІДЧИЙ НАСТУП

Наступна спроба дала дуже дивний результат.

У мене є дві окремі сторінки, які є споживачами контексту. Різниця між ними полягає в тому, що одна є функцією, а інша - складовою класу.

У компоненті функції я можу відтворити {authUser.email}. Коли я намагаюся зробити те ж саме в компоненті класу, я отримую помилку, яка говорить:

TypeError: Неможливо прочитати властивість 'email' з null

Ця помилка надходить з одного сеансу з тим самим авторизованим користувачем.

Примітка. Хоча в документації на базу даних говориться про те, що властивість currentUser доступна на аутентифікації - я взагалі не зміг змусити її працювати.

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

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 автентифікованого користувача, з цього компонента я можу вивести атрибут електронної пошти в колекцію auth для цього користувача.

Хоча документація Firebase надає цю пораду щодо управління користувачами та доступу до атрибутів тут, я не знайшов способу реалізувати цей підхід в реагуванні. Кожна варіація спроби зробити це, як створення помічників у моєму firebase.js, так і намагання почати з нуля в компоненті створює помилки в доступі до firebase. Однак я можу створити список користувачів та пов’язану з ними інформацію про колекцію користувачів (я не можу отримати користувача на основі того, хто є користувачем).

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

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;

НАСЛІДЧИЙ НАСТУП

Це дійсно дивно, що за допомогою цієї спроби я намагаюся консолювати журнал значень, які я можу бачити, що існують у базі даних, і значення імені повертається як "невизначене", де в db є рядок.

Ця спроба:

    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 ('користувачі'). doc (authUser.uid) .get ()

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

    } 

Що я не можу зробити, хоча знайти спосіб видати це ім’я в jsx

Як ви насправді друкуєте вихід?

Коли я намагаюся:

{ 


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

                }

Я отримую помилку, яка говорить:

TypeError: this.props.firebase.db.collection (...). Doc (...). Get (...) Дані не є функцією

Коли я намагаюся:

{ 



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

Я отримую помилку, яка говорить:

Рядок 281: 23: Очікується призначення або виклик функції, а замість цього з'явився вираз "невикористані" вирази

Коли я намагаюся:

{ 


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

Повідомлення про помилку говорить:

Очікували виклик призначення чи функції та замість цього бачили вираз

Я готовий відмовитись від спроб дізнатися, як змусити працювати запити знімків - якщо я можу просто отримати ім’я колекції користувачів для відображення на екрані. Хтось може допомогти в цьому кроці?

НАСЛІДЧИЙ НАСТУП

Я знайшов цю посаду . У ньому є чітке пояснення того, що має відбутися, але я не можу реалізувати це, як показано, оскільки компонентDidMount не знає, що таке authUser.

Моя поточна спроба полягає в наступному - однак, як написано в даний час, authUser є обгорткою повернутого значення - і сегмент компонентаDidMount не знає, що таке 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, щоб цілий компонент міг ним користуватися - тим самим дозволяючи мені отримати доступ до зареєстрованого користувача у функції компонентаDidMount.

Я змінив маршрут на:

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

та вилучив споживача із заяви про надання візуальної частини панелі інструментів.

Потім у компоненті компонент DidMount на панелі інструментів я спробував:

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: Function CollectionReference.doc () вимагає, щоб його перший аргумент був нетиповим рядком типу, але це: спеціальний об'єкт DocumentReference

НАЙКРАЩА УВАГА Люди внизу, здається, знайдуть щось корисне в першому запропонованому рішенні. Я не зміг знайти в ньому нічого корисного, але читаючи його пропозиції, я намагаюся бачити, як приклад в документації на базу даних (він не розкриває, як надати: 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!");
      }

принципово відрізняється від моєї спроби у функції компонентаDidMount, а саме:

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

    );  
  }

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

З цією метою я бачу з прикладу лабораторії коду 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 - ваш код є поліпшенням моєї спроби? Я не можу знайти спосіб, який допоможе автору користувачу надати мені доступ до колекції користувачів, яка має той самий uid. Я намагаюся зрозуміти вашу пропозицію щодо коду - як це покращиться в моєму як перший крок. Будь ласка, ви можете визначити, який біт це поліпшення / виправлення? Дякую
Мел

Що станеться, якщо ти зробиш this.auth.onAuthStateChanged(authUser => { if (authUser) {console.log(authUser.uid) })?
Рено Тарнек

Я можу вивести всі властивості authUser (дані колекції аутентифікації). Я не можу отримати до призначених для користувача даних відповідного документа в колекції користувача , який має ідентифікатор користувача в якості ідентифікатора
Мел

1

У мене є теорія, яку я хотів би, щоб ви перевірили.

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

Причина, по якій ваш код не працює, як очікувалося, полягає в тому, що там, де ви телефонуєте next(authUser), він знаходиться всередині then()ланцюга Promise. Будь-які помилки, закинуті всередину Обіцянки, будуть виявлені та спричинить відхилення Обіцянки. Коли Обіцянка відхилена, вона називатиме додані обробники помилок із помилкою. У ланцюзі Обіцянь, про яку йдеться, наразі немає такого обробника помилок.

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

То що ми можемо зробити, щоб уникнути подібної ситуації? Найпростіше було б зателефонувати next(authUser) за межі сфери then()обробника програми Promise . Ми можемо це зробити, використовуючи window.setTimeout(function).

Отже, у своєму коді ви б замінили

next(authUser)

з

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

Це призведе до помилок як звичайних, а не до того, що потрапить у ланцюжок Обіцянь.

Важливо те, що у вас немає обробника лову, який обробляє, коли 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();
  }
}

Дякую! Я спробую це сьогодні ввечері. Я раді дізнатися про це в будь-якому випадку. Невдовзі повернусь із відгуками.
Мел

Привіт Сем - дякую, що запропонували цю пропозицію. Я зайняв деякий час, щоб ознайомитися з документацією, яку ви зв'язали, і в мене було кілька запитів на це. Поки я вдячний за допомогу - це не вирішило моєї проблеми. Коли я намагаюся отримати доступ до атрибутів колекції користувачів, я все ще отримую помилку, яка говорить: TypeError: Неможливо прочитати властивість 'user' із невизначеного
Mel

@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, що продовжує намагатися мені допомогти. Я на це подивився. Моя мета - прочитати uid автора authUser, щоб я міг використовувати його, щоб отримати атрибути пов'язаної колекції користувачів для цього користувача (ім'я в колекції користувачів має більше, ніж displayName в колекції auth Firebase, тому я не намагаюся читати властивості таблиці автентифікації
Мел

Запропонована зміна функції слухача компонентDidMount не призвела до помилки - але вона не працювала. Коли він намагається зафіксувати значення authUser в компоненті панелі інструментів за допомогою цього слухача - я отримую помилку, кажучи, що authUser не визначено. Я не отримую цієї помилки, коли використовую компонентDidMount для AuthContext.Provider так, як я його визначив. Дякуємо за інформацію про випадкові властивості в повідомленнях журналу.
Мел

@Mel Чи можете ви підтвердити, що останній рядок вашого Dashboardфайлу export default withAuthentication(Dashboard);(а не він withFirebase)
samthecodingman

Підтверджено. withFirebase вбудований в withAuthentication - тому він потрапляє через цей HOC.
Мел

@Mel Чи можете ви перевірити повідомлення, які я вам надіслав на Slack
samthecodingman,

0

Якщо хтось подібний застряг, я знайшов рішення тут: Firebase & React: Тип аргументу CollectionReference.doc ()

Він не працює на оновлення сторінки (все-таки видає помилку, що uid недійсний), але реагуючі гачки на використанняEffect повинні замінити функцію компонентDidMount комбінацією Mount and Update. Я намагаюся, що далі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.