Jak uzyskać szczegółowe informacje o bazie danych użytkownika związane z authUser w Firestore?


10

Próbuję dowiedzieć się, jak uzyskać nazwę użytkownika, który jest atrybutem przechowywanym w kolekcji użytkownika, która została połączona z atrybutami utworzonymi przez model uwierzytelniania bazy ogniowej.

Mogę uzyskać dostęp do authUser - co daje mi ograniczone pola, które baza ogniowa zbiera w narzędziu uwierzytelniającym, a następnie próbuję dostać się stamtąd do powiązanej kolekcji użytkowników (która używa tego samego identyfikatora użytkownika).

Mam kontekstowego konsumenta reagującego na:

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

Następnie w moim komponencie próbuję użyć:

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

W moim firebase.js - myślę, że próbowałem scalić atrybuty authUser z modelu Uwierzytelniania z atrybutami kolekcji użytkowników w następujący sposób:

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

Nie mogę znaleźć sposobu na przejście od authUser (który działa, aby dostać się do atrybutów Uwierzytelniania) - do kolekcji użytkownika, która ma identyfikator z tym samym identyfikatorem użytkownika z kolekcji Uwierzytelnianie.

Widziałem ten post , który wydaje się mieć ten sam problem, i próbowałem ustalić, na co ma sugerować odpowiedź - ale nie mogę znaleźć sposobu, który działałby, aby przejść z kolekcji uwierzytelniania do kolekcji użytkownika i nie wiem, co robi dla mnie scalanie, jeśli nie daje mi dostępu do atrybutów w kolekcji użytkowników z authUser.

Próbowałem użyć pomocnika w moim firebase.js, aby dać mi użytkownika z UID - ale to też nie pomaga.

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

Następna próba

Aby dodać więcej tła, stworzyłem komponent testowy, który może rejestrować (ale nie renderować) authUser w następujący sposób:

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;

Dziennik zawiera szczegółowe informacje dotyczące identyfikatora użytkownika, adresu e-mail itp., Ale znajduje się wśród długiej listy elementów - z których wiele jest poprzedzonych 1 lub 2 literami (nie mogę znaleźć klucza, aby dowiedzieć się, co każdy z tych prefiksów litery oznaczają). Przykład wyodrębniony poniżej:

wprowadź opis zdjęcia tutaj

AKTUALIZACJA TEGO KOMENTARZA:

Wcześniej powiedziałem: pola uid, e-mail itp. Nie wydają się być zagnieżdżone pod tymi prefiksami, ale jeśli spróbuję:

 console.log(authUser.email)

, Pojawia się komunikat o błędzie:

TypeError: Nie można odczytać właściwości „email” o wartości null

Aktualizacja: Właśnie zdałem sobie sprawę, że w dzienniku konsoli muszę rozwinąć menu rozwijane z etykietą:

Q {I: Array (0), l:

aby zobaczyć atrybut e-mail. Czy ktoś wie, do czego nawiązuje ten jibberish? Nie mogę znaleźć klucza, aby dowiedzieć się, co oznacza Q, I lub l, aby wiedzieć, czy mam się odwoływać do tych rzeczy, aby uzyskać dostęp do odpowiednich atrybutów w tabeli uwierzytelniania. Może jeśli uda mi się to rozgryźć - znajdę sposób na dostanie się do kolekcji użytkowników za pomocą identyfikatora użytkownika z kolekcji Uwierzytelnianie.

Czy ktoś używał reagowania na interfejsie, z konsumentem kontekstowym, aby dowiedzieć się, kto jest bieżącym użytkownikiem? Jeśli tak, to w jaki sposób uzyskujesz dostęp do ich atrybutów w modelu Uwierzytelniania i jak uzyskałeś dostęp do atrybutów w powiązanej kolekcji Użytkowników (gdzie dokument ID w dokumencie użytkownika jest identyfikatorem UID z tabeli Uwierzytelniania)?

NASTĘPNA PRÓBA

Kolejna próba przyniosła bardzo dziwny wynik.

Mam 2 oddzielne strony, które są konsumentami kontekstowymi. Różnica między nimi polega na tym, że jedna jest funkcją, a druga jest składnikiem klasy.

W komponencie funkcji mogę renderować {authUser.email}. Kiedy próbuję zrobić to samo w komponencie klasy, pojawia się błąd:

TypeError: Nie można odczytać właściwości „email” o wartości null

Ten błąd pochodzi z tej samej sesji z tym samym zalogowanym użytkownikiem.

Uwaga: chociaż dokumentacja bazy ogniowej mówi, że właściwość currentUser jest dostępna podczas uwierzytelniania - nie udało mi się w ogóle sprawić, by działała.

Mój składnik funkcji ma:

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;

Chociaż nie mogę uzyskać dostępu do atrybutów kolekcji użytkownika, w których docId w dokumencie użytkownika jest taki sam jak identyfikator użytkownika uwierzytelnionego użytkownika, z tego komponentu mogę wypisać atrybut e-mail w kolekcji auth dla tego użytkownika.

Chociaż dokumentacja Firebase zawiera porady dotyczące zarządzania użytkownikami i uzyskiwania dostępu do atrybutów tutaj, nie znalazłem sposobu na wdrożenie tego podejścia w reakcji. Każda odmiana próby zrobienia tego, zarówno poprzez tworzenie pomocników w moim firebase.js, jak i próbę rozpoczęcia od zera w komponencie, powoduje błędy w dostępie do bazy ogniowej. Mogę jednak utworzyć listę użytkowników i powiązane z nimi informacje o kolekcji użytkowników (nie mogę uzyskać użytkownika na podstawie tego, kto jest authUser).

Mój komponent klasy ma:

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;

W moim AuthContext.Provider - mam:

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;

NASTĘPNA PRÓBA

To naprawdę dziwne, że przy tej próbie próbuję zapisać w konsoli wartości, które widzę w bazie danych, a wartość nazwy jest zwracana jako „niezdefiniowana”, w której db ma ciąg znaków.

Ta próba ma:

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

NASTĘPNA PRÓBA

Ta próba jest niezdarna i nie wykorzystuje pomocników ani zapytań migawkowych, których próbowałem użyć powyżej, ale rejestruję atrybuty dokumentu kolekcji użytkownika w konsoli w następujący sposób:

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

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

    } 

Nie mogę jednak znaleźć sposobu na wyrenderowanie tej nazwy w jsx

Jak faktycznie drukujesz wydruk?

Kiedy próbuję:

{ 


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

                }

Pojawia się komunikat o błędzie:

TypeError: this.props.firebase.db.collection (...). Doc (...). Get (...). Data nie jest funkcją

Kiedy próbuję:

{ 



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

Pojawia się komunikat o błędzie:

Wiersz 281: 23: Oczekiwano przypisania lub wywołania funkcji i zamiast tego zobaczyłem wyrażenie niewykorzystane-wyrażenia

Kiedy próbuję:

{ 


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

Komunikat o błędzie mówi:

Oczekiwano przypisania lub wywołania funkcji i zamiast tego zobaczyłem wyrażenie

Jestem gotów zrezygnować z próby znalezienia sposobu na uruchomienie zapytań migawkowych - jeśli mogę tylko wyświetlić nazwę kolekcji użytkowników do renderowania na ekranie. Czy ktoś może pomóc w tym kroku?

NASTĘPNA PRÓBA

Znalazłem ten post . Ma dobre wyjaśnienie tego, co musi się wydarzyć, ale nie mogę tego zaimplementować, jak pokazano, ponieważ componentDidMount nie wie, co to jest authUser.

Moja bieżąca próba jest następująca - jednak, jak obecnie napisano, authUser jest opakowaniem zwracanej wartości - a segment componentDidMount nie wie, co to jest 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);

NASTĘPNA PRÓBA

Następnie próbowałem zawinąć trasę dla deski rozdzielczej wewnątrz AuthContext.Consumer, aby cały komponent mógł z niej korzystać - pozwalając w ten sposób uzyskać dostęp do zalogowanego użytkownika w funkcji componentDidMount.

Zmieniłem trasę na:

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

i usunąłem konsumenta z instrukcji renderowania komponentu deski rozdzielczej.

Następnie w componentDidMount w komponencie Dashboard próbowałem:

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

Gdy próbuję tego, pojawia się komunikat o błędzie:

FirebaseError: Funkcja CollectionReference.doc () wymaga, aby pierwszy argument był niepustym ciągiem typu, ale był to: niestandardowy obiekt DocumentReference

NASTĘPNA PRÓBA Ludzie poniżej wydają się znaleźć coś pomocnego w pierwszym proponowanym rozwiązaniu. Nie udało mi się znaleźć w nim nic przydatnego, ale czytając jego sugestie, staram się zobaczyć, jak przykład w dokumentacji bazy ogniowej (nie ujawnia, jak nadać wartość: uid żądaniu .doc () ), który jest następujący:

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

różni się zasadniczo od mojej próby w funkcji componentDidMount, która jest:

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

    );  
  }

Być może rozwiązanie tego kroku jest wskazówką, która pomoże to osiągnąć w wyniku. Czy ktoś może znaleźć lepszą dokumentację sklepu przeciwpożarowego, która pokaże, jak uzyskać rekord kolekcji użytkownika za pomocą zalogowanego identyfikatora użytkownika?

W tym celu z przykładu laboratorium kodu FriendlyEats widzę , że istnieje próba podania doc.id wartości wyszukiwania id w kodzie. Nie wiem, w jakim języku jest napisany ten kod - ale wygląda podobnie do tego, co próbuję zrobić - po prostu nie widzę, jak przejść od tego przykładu do czegoś, z czym wiem, jak pracować.

display: function(doc) {
      var data = doc.data();
      data['.id'] = doc.id;
      data['go_to_restaurant'] = function() {
        that.router.navigate('/restaurants/' + doc.id);
      };

Dla waszej terminologii wasza terminologia nie jest do końca właściwa, co utrudnia czytanie tego pytania. W Firebase nie ma nic, co można by nazwać „stołem”. W Firebase Auth są tylko użytkownicy - nie ma „tabeli uwierzytelniania”. W Firestore znajdują się kolekcje i dokumenty w tych kolekcjach, ale nie ma tabel. Próbuję dowiedzieć się, gdzie utkniesz i jak pokazany kod nie działa w oczekiwany sposób, ale po prostu nie składam go w całość. Zastanów się nad edycją pytania, aby użyć bardziej standardowej terminologii, w której można znaleźć dokumenty, i wyjaśnij, co nie działa zgodnie z oczekiwaniami.
Doug Stevenson

Dobra - chętnie zastępujemy tabele kolekcjami. Punkt jest nadal ten sam.
Mel

Chodzi mi o to, że tak naprawdę nie mogłem zrozumieć twojego punktu, a terminologia nie pomogła. Czy możesz wyjaśnić bardziej szczegółowo, co nie działa w pokazanym kodzie? Czy coś nie działało zgodnie z oczekiwaniami? Zilustrować jakieś błędy lub dzienniki debugowania?
Doug Stevenson

Nic nie działa. Spodziewam się znaleźć sposób na dostęp do szczegółów kolekcji użytkowników od nasłuchiwania authUser. authUser jest zdefiniowany w module obsługi kontekstu i metodzie klasy pokrewnej, która nasłuchuje zmian w metodzie. Nie mogę ominąć atrybutów w kolekcji uwierzytelniania - próbuję uzyskać dostęp do powiązanej kolekcji użytkowników w firestore. Brak dzienników. Tylko komunikaty o błędach, które mówią, że pole jest niezdefiniowane.
Mel

1
Proponuję zacząć od prostego zadania, spraw, by zadziałało, aby zdobyć trochę doświadczenia, a następnie zastosuj to do bardziej złożonych problemów, takich jak ten, który masz teraz. W dokumentacji nie ma nic zasadniczo niepoprawnego (wiem, ponieważ używam jej cały czas i pomagam ludziom z tym samym). Aby uzyskać pomoc dotyczącą przepełnienia stosu, musisz zilustrować konkretny problem, najlepiej MCVE, którego każdy może użyć do odtworzenia problemu. Samo powiedzenie „nie mogę zmusić do pracy” nie wystarczy. stackoverflow.com/help/minimal-reproducible-example
Doug Stevenson

Odpowiedzi:


5

Rozumiem z ostatniego wiersza twojego pytania ( users = () => this.db.collection('users');), że kolekcja, w której przechowujesz dodatkowe informacje o użytkownikach, jest nazywana usersi że dokument użytkownika w tej kolekcji używa userId (uid) jako docId.

Następujące czynności powinny wykonać lewę (niesprawdzone):

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

Tak więc w obserwatorze ustawionym za pomocą onAuthStateChanged()metody, gdy wykrywamy, że użytkownik jest zalogowany (tj. W if (authUser) {}), używamy go uiddo zapytania o unikalny dokument odpowiadający temu użytkownikowi w userskolekcji (zobacz jeden dokument i dokument get()metoda).


Czy jest więc coś nie tak ze sposobem, w jaki zdefiniowałem naAuthUserListener? Więc jeśli wypróbuję twoje poprawki do tej metody, co mam zrobić, aby dostać się do kolekcji użytkowników od authUser?
Mel

„Więc jest coś nie tak ze sposobem, w jaki zdefiniowałem na AuthUserListener?” -> nie z tego, co widzę. „co mam zrobić, aby dostać się do kolekcji użytkowników od authUser?” -> Jeśli dobrze rozumiem, chcesz uzyskać JEDEN dokument, a nie kolekcję. Kod w odpowiedzi powinien działać.
Renaud Tarnec

Chcę uzyskać authUser - czy Twój kod jest ulepszeniem mojej próby? Nie mogę znaleźć sposobu, który sprawiłby, że authUser dałby mi dostęp do kolekcji użytkowników, która ma ten sam identyfikator użytkownika. Próbuję zrozumieć twoją sugestię kodu - jak to poprawia moją, jako pierwszy krok. Czy możesz zidentyfikować, który fragment jest poprawą / korektą? Dziękuję
Mel

Co się stanie, jeśli to zrobisz this.auth.onAuthStateChanged(authUser => { if (authUser) {console.log(authUser.uid) })?
Renaud Tarnec,

Mogę wyprowadzić wszystkie właściwości authUser (dane kolekcji uwierzytelniania). Nie mogę uzyskać dostępu do danych użytkownika z powiązanego dokumentu w kolekcji użytkownika, który ma identyfikator użytkownika jako identyfikator
Mel

1

Mam teorię, którą chciałbym, abyście przetestowali.

Myślę, że kiedy dzwonisz next(authUser)w swoim onAuthStateChangedmodule obsługi, podczas jego wykonywania napotyka błąd (np. cannot read property 'name' of undefined at ...).

Kod nie działa zgodnie z oczekiwaniami, ponieważ tam, gdzie dzwonisz next(authUser), znajduje się on then()w łańcuchu obietnic. Wszelkie błędy wyrzucone w ramach Obietnicy zostaną przechwycone i spowodują odrzucenie Obietnicy. Gdy obietnica zostanie odrzucona, wywoła dołączone procedury obsługi błędów wraz z błędem. W omawianym łańcuchu obietnic nie ma obecnie takiej procedury obsługi błędów.

Jeśli cię zgubiłem, przeczytaj ten post na blogu na kursie awaryjnym Promises, a następnie wróć.

Co więc możemy zrobić, aby uniknąć takiej sytuacji? Najprostszym byłoby wywołanie next(authUser) pozathen() zakresem obsługi Obietnicy . Możemy to zrobić za pomocąwindow.setTimeout(function) .

W swoim kodzie zastąpiłbyś

next(authUser)

z

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

Spowoduje to rzucenie błędów jak zwykle, zamiast zostać złapanym przez łańcuch obietnic.

Co ważne, nie masz modułu obsługi przechwytywania, który obsługuje się w przypadku userDocRef.get()niepowodzenia. Więc po prostu dodaj .catch(() => setTimeout(fallback))na końcuthen() , aby Twój kod używał metody rezerwowej, jeśli wystąpi błąd.

W rezultacie otrzymujemy:

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

Edytowany kod

Powyższe wyjaśnienie powinno pozwolić ci naprawić kod, ale oto moja wersja twojej Firebaseklasy z różnymi zmianami łatwości użycia.

Stosowanie:

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
}

Definicja klasy:

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

Zauważ, że w tej wersji onUserDataListenermetoda zwraca funkcję anulowania subskrypcji onAuthStateChanged. Kiedy twój komponent jest odmontowany, powinieneś odłączyć wszystkie odpowiednie nasłuchiwania, aby nie doszło do wycieku pamięci lub nie działał kod działający w tle, gdy nie był potrzebny.

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

Dziękuję Ci! Spróbuję tego dziś wieczorem. Jestem podekscytowany, aby dowiedzieć się o tym w obu przypadkach. Wrócę wkrótce z opinią.
Mel

Cześć Sam - dziękuję za zaoferowanie tej sugestii. Poświęciłem trochę czasu na zapoznanie się z dokumentacją, którą podłączyłeś, i miałem na to kilka uwag. Chociaż jestem wdzięczny za pomoc - to nie rozwiązało mojego problemu. Kiedy próbuję uzyskać dostęp do atrybutów kolekcji użytkowników, nadal pojawia się błąd: TypeError: Nie można odczytać właściwości „user” niezdefiniowanej
Mel

@Mel Czy po uruchomieniu oryginalnego kodu zostałeś powiadomiony o błędzie TypeError? Po raz pierwszy o tym wspomniałeś. Co oznacza, że ​​ten kod wyrzucił błąd poza zakres Obietnicy. Czy możesz podać wynik console.log(snapshot.data())?
samthecodingman

Próbowałem tego - komunikat o błędzie brzmi: TypeError: snapshot.data nie jest funkcją
Mel

Będę się starał to przenosić - może nie próbuję zalogować się w dobrym miejscu
Mel

0

W swojej AuthContext.Providerimplementacji uzyskujesz onAuthStateChanged bezpośredni dostęp do nasłuchiwania zestawu SDK :

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

Należy to zmienić, aby użyć onAuthUserListenerklasy pomocniczej:

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

Jeśli chodzi o komunikaty dziennika wypełnione wieloma losowymi właściwościami, dzieje się tak dlatego, że firebase.Userobiekt ma zarówno publiczny interfejs API, jak i implementację z szeregiem prywatnych właściwości i metod, które są minimalizowane podczas kompilacji. Ponieważ te zminimalizowane właściwości i metody nie są wyraźnie oznaczone jako niepoliczalne, są one zawarte w wynikach dziennika. Jeśli zamiast tego chcesz rejestrować tylko te części, które są rzeczywiście przydatne, możesz zniszczyć i zrestrukturyzować obiekt za pomocą:

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

Dzięki @samthecodingman za kontynuowanie próby mi pomóc. Rzuciłem na to okiem. Moim celem jest odczytanie identyfikatora użytkownika authUser, dzięki czemu mogę go użyć, aby uzyskać atrybuty powiązanej kolekcji użytkownika dla tego użytkownika (nazwa w kolekcji użytkownika ma więcej niż displayName w kolekcji autb firebase - więc nie próbuję czytaj właściwości tabeli uwierzytelniania
Mel

Sugerowana zmiana funkcji komponentu detektora komponentu nasłuchiwania nie zgłosiła błędu - ale nie działała. Podczas próby zarejestrowania wartości authUser w komponencie deski rozdzielczej za pomocą tego detektora - pojawia się błąd informujący, że authUser nie jest zdefiniowany. Ten błąd nie pojawia się, gdy używam componentDidMount dla AuthContext.Provider w sposób, w jaki go zdefiniowałem. Dziękujemy za informację o losowych właściwościach w komunikatach dziennika.
Mel

@Mel Czy możesz potwierdzić, że ostatni wiersz twojego Dashboard pliku to export default withAuthentication(Dashboard);(i nie withFirebase)
samthecodingman

Potwierdzony. withFirebase jest wbudowany w Uwierzytelnianie - więc jest odbierany przez ten HOC.
Mel

@Mel Czy możesz sprawdzić wiadomości, które wysłałem ci na
Slacku

0

Jeśli ktoś jeszcze podobnie utknął, znalazłem rozwiązanie tutaj: typ argumentu Firebase & React: CollectionReference.doc ()

Nie działa przy odświeżaniu strony (nadal zgłasza błąd, że UID ma wartość NULL), ale reakcje przechwytujące na useEffect powinny zastąpić funkcję componentDidMount kombinacją Mount i Update. Próbuję tego następnego.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.