Firebase-Listener mit React Hooks


27

Ich versuche herauszufinden, wie ein Firebase-Listener verwendet wird, damit Cloud-Firestore-Daten mit React Hooks-Updates aktualisiert werden.

Anfangs habe ich dies mit einer Klassenkomponente mit einer componentDidMount-Funktion gemacht, um die Firestore-Daten abzurufen.

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

Das bricht ab, wenn die Seite aktualisiert wird, also versuche ich herauszufinden, wie ich den Listener bewegen kann, um auf Hooks zu reagieren.

Ich habe das React-Firebase-Hooks- Tool installiert - obwohl ich nicht herausfinden kann, wie ich die Anweisungen lesen soll, damit es funktioniert.

Ich habe eine Funktionskomponente wie folgt:

import React, { useState, useEffect } from 'react';
import { useDocument } from 'react-firebase-hooks/firestore';

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, withAuthentication } from '../Session/Index';

function Dashboard2(authUser) {
    const FirestoreDocument = () => {

        const [value, loading, error] = useDocument(
          Firebase.db.doc(authUser.uid),
          //firebase.db.doc(authUser.uid),
          //firebase.firestore.doc(authUser.uid),
          {
            snapshotListenOptions: { includeMetadataChanges: true },
          }
        );
    return (

        <div>    



                <p>
                    {error && <strong>Error: {JSON.stringify(error)}</strong>}
                    {loading && <span>Document: Loading...</span>}
                    {value && <span>Document: {JSON.stringify(value.data())}</span>}
                </p>




        </div>

    );
  }
}

export default withAuthentication(Dashboard2);

Diese Komponente wird wie folgt in einen authUser-Wrapper auf Routenebene eingeschlossen:

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

Ich habe eine firebase.js-Datei, die wie folgt in den Firestore eingesteckt wird:

class Firebase {
  constructor() {
    app.initializeApp(config).firestore();
    /* helpers */
    this.fieldValue = app.firestore.FieldValue;


    /* Firebase APIs */
    this.auth = app.auth();
    this.db = app.firestore();


  }

Außerdem wird ein Listener definiert, der weiß, wann sich der AuthUser ändert:

onAuthUserListener(next, fallback) {
    // onUserDataListener(next, fallback) {
      return this.auth.onAuthStateChanged(authUser => {
        if (authUser) {
          this.user(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('An error occured -> ', err.code ? err.code + ': ' + err.message : (err.message || err));
            setTimeout(fallback, 0); // escapes this Promise's error handler
          });

        };
        if (!authUser) {
          // user not logged in, call fallback handler
          fallback();
          return;
        }
    });
  };

Dann habe ich in meinem Firebase-Kontext-Setup:

import FirebaseContext, { withFirebase } from './Context';
import Firebase from '../../firebase';
export default Firebase;
export { FirebaseContext, withFirebase };

Der Kontext wird in einem withFirebase-Wrapper wie folgt eingerichtet:

import React from 'react';
const FirebaseContext = React.createContext(null);

export const withFirebase = Component => props => (
  <FirebaseContext.Consumer>
    {firebase => <Component {...props} firebase={firebase} />}
  </FirebaseContext.Consumer>
);
export default FirebaseContext;

Dann habe ich in meinem withAuthentication-HOC einen Kontextanbieter wie folgt:

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;

Derzeit - wenn ich dies versuche, erhalte ich eine Fehlermeldung in der Dashboard2-Komponente, die besagt:

Firebase 'ist nicht definiert

Ich habe Firebase in Kleinbuchstaben ausprobiert und den gleichen Fehler erhalten.

Ich habe auch firebase.firestore und Firebase.firestore ausprobiert. Ich bekomme den gleichen Fehler.

Ich frage mich, ob ich mein HOC nicht mit einer Funktionskomponente verwenden kann.

Ich habe diese Demo-App und diesen Blog-Beitrag gesehen .

Den Ratschlägen im Blog folgend, habe ich eine neue firebase / contextReader.jsx erstellt mit:

 import React, { useEffect, useContext } from 'react';
import Firebase from '../../firebase';



export const userContext = React.createContext({
    user: null,
  })

export const useSession = () => {
    const { user } = useContext(userContext)
    return user
  }

  export const useAuth = () => {
    const [state, setState] = React.useState(() => 
        { const user = firebase.auth().currentUser 
            return { initializing: !user, user, } 
        }
    );
    function onChange(user) {
      setState({ initializing: false, user })
    }

    React.useEffect(() => {
      // listen for auth state changes
      const unsubscribe = firebase.auth().onAuthStateChanged(onChange)
      // unsubscribe to the listener when unmounting
      return () => unsubscribe()
    }, [])

    return state
  }  

Dann versuche ich, meine App.jsx in diesen Reader zu packen mit:

function App() {
  const { initializing, user } = useAuth()
  if (initializing) {
    return <div>Loading</div>
  }

    // )
// }
// const App = () => (
  return (
    <userContext.Provider value={{ user }}> 


    <Router>
        <Navigation />
        <Route path={ROUTES.LANDING} exact component={StandardLanding} />

Wenn ich das versuche, erhalte ich eine Fehlermeldung:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth ist keine Funktion

Ich habe diesen Beitrag gesehen, der sich mit diesem Fehler befasst, und habe versucht, Garn zu deinstallieren und neu zu installieren. Es macht keinen Unterschied.

Wenn ich mir die Demo-App anschaue , wird vorgeschlagen, dass der Kontext mithilfe einer 'Interface'-Methode erstellt werden sollte. Ich kann nicht sehen, woher das kommt - ich kann keine Referenz finden, um es in der Dokumentation zu erklären.

Ich kann die Anweisungen nur verstehen, wenn ich versuche, das zu tun, was ich getan habe, um dies anzuschließen.

Ich habe diesen Beitrag gesehen, der versucht, Firestore anzuhören, ohne React-Firebase-Hooks zu verwenden. Die Antworten verweisen auf den Versuch, herauszufinden, wie dieses Tool verwendet wird.

Ich habe diese ausgezeichnete Erklärung gelesen, die beschreibt, wie man sich von HOCs zu Haken bewegt. Ich bin nicht sicher, wie ich den Firebase-Listener integrieren soll.

Ich habe diesen Beitrag gesehen, der ein hilfreiches Beispiel dafür liefert, wie man darüber nachdenkt. Ich bin mir nicht sicher, ob ich dies in der authListener-KomponenteDidMount versuchen soll - oder in der Dashboard-Komponente, die versucht, es zu verwenden.

NÄCHSTER VERSUCH Ich habe diesen Beitrag gefunden , der versucht, das gleiche Problem zu lösen.

Wenn ich versuche, die von Shubham Khatri angebotene Lösung zu implementieren, richte ich die Firebase-Konfiguration wie folgt ein:

Ein Kontextanbieter mit: import React, {useContext} from 'react'; Firebase aus '../../firebase' importieren;

const FirebaseContext = React.createContext(); 

export const FirebaseProvider = (props) => ( 
   <FirebaseContext.Provider value={new Firebase()}> 
      {props.children} 
   </FirebaseContext.Provider> 
); 

Der Kontext-Hook hat dann:

import React, { useEffect, useContext, useState } from 'react';

const useFirebaseAuthentication = (firebase) => {
    const [authUser, setAuthUser] = useState(null);

    useEffect(() =>{
       const unlisten = 
firebase.auth.onAuthStateChanged(
          authUser => {
            authUser
              ? setAuthUser(authUser)
              : setAuthUser(null);
          },
       );
       return () => {
           unlisten();
       }
    });

    return authUser
}

export default useFirebaseAuthentication;

Dann verpacke ich in der index.js die App im Provider wie folgt:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App/Index';
import {FirebaseProvider} from './components/Firebase/ContextHookProvider';

import * as serviceWorker from './serviceWorker';


ReactDOM.render(

    <FirebaseProvider> 
    <App /> 
    </FirebaseProvider>,
    document.getElementById('root')
);

    serviceWorker.unregister();

Wenn ich dann versuche, den Listener in der Komponente zu verwenden, die ich habe:

import React, {useContext} from 'react';
import { FirebaseContext } from '../Firebase/ContextHookProvider';
import useFirebaseAuthentication from '../Firebase/ContextHook';


const Dashboard2 = (props) => {
    const firebase = useContext(FirebaseContext);
    const authUser = 
useFirebaseAuthentication(firebase);

    return (
        <div>authUser.email</div>
    )
 }

 export default Dashboard2;

Und ich versuche es als Route ohne Komponenten oder Auth-Wrapper zu verwenden:

<Route path={ROUTES.DASHBOARD2} component={Dashboard2} />

Wenn ich das versuche, erhalte ich eine Fehlermeldung:

Importversuch: 'FirebaseContext' wird nicht aus '../Firebase/ContextHookProvider' exportiert.

Diese Fehlermeldung ist sinnvoll, da ContextHookProvider FirebaseContext nicht exportiert - es exportiert FirebaseProvider -, aber wenn ich nicht versuche, dies in Dashboard2 zu importieren, kann ich in der Funktion, die versucht, es zu verwenden, nicht darauf zugreifen.

Ein Nebeneffekt dieses Versuchs ist, dass meine Anmeldemethode nicht mehr funktioniert. Es wird jetzt eine Fehlermeldung generiert, die besagt:

TypeError: Die Eigenschaft 'doCreateUserWithEmailAndPassword' von null kann nicht gelesen werden

Ich werde dieses Problem später lösen - aber es muss eine Möglichkeit geben, herauszufinden, wie man mit Firebase reagiert, ohne Monate dieser Schleife durch Millionen von Wegen zu führen, die nicht funktionieren, um ein grundlegendes Authentifizierungssetup zu erhalten. Gibt es ein Starter-Kit für Firebase (Firestore), das mit Reaktionshaken funktioniert?

Nächster Versuch Ich habe versucht, dem Ansatz in diesem udemy-Kurs zu folgen - aber es funktioniert nur, um eine Formulareingabe zu generieren. Es gibt keinen Listener, der die Routen umgibt , um sie an den authentifizierten Benutzer anzupassen.

Ich habe versucht, dem Ansatz in diesem Youtube-Tutorial zu folgen - mit dem dieses Repo funktioniert. Es zeigt, wie Hooks verwendet werden, nicht jedoch, wie der Kontext verwendet wird.

NÄCHSTER VERSUCH Ich habe dieses Repo gefunden , das einen gut durchdachten Ansatz für die Verwendung von Hooks mit Firestore zu haben scheint. Ich kann den Code jedoch nicht verstehen.

Ich habe dies geklont und versucht, alle öffentlichen Dateien hinzuzufügen. Wenn ich es dann ausführe, kann ich den Code nicht zum Laufen bringen. Ich bin mir nicht sicher, was in den Anweisungen fehlt, um dies zum Laufen zu bringen, um festzustellen, ob der Code Lektionen enthält, die zur Lösung dieses Problems beitragen können.

NÄCHSTER VERSUCH

Ich habe die Divjoy-Vorlage gekauft, die als Setup für Firebase beworben wird (sie ist nicht für Firestore eingerichtet, falls jemand anderes dies als Option in Betracht zieht).

Diese Vorlage schlägt einen Auth-Wrapper vor, der die Konfiguration der App initialisiert - jedoch nur für die Auth-Methoden - und daher neu strukturiert werden muss, damit ein anderer Kontextanbieter für den Firestore zugelassen wird. Wenn Sie diesen Prozess durcheinander bringen und den in diesem Beitrag gezeigten Prozess verwenden, bleibt ein Fehler im folgenden Rückruf übrig:

useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      if (user) {
        setUser(user);
      } else {
        setUser(false);
      }
    });

Es weiß nicht, was Feuerbasis ist. Dies liegt daran, dass es im Firebase-Kontextanbieter definiert ist, der importiert und definiert wird (in der Funktion useProvideAuth ()) als:

  const firebase = useContext(FirebaseContext)

Ohne Chancen auf den Rückruf lautet der Fehler:

React Hook useEffect hat eine fehlende Abhängigkeit: 'firebase'. Schließen Sie es entweder ein oder entfernen Sie das Abhängigkeitsarray

Wenn ich versuche, diese Konstante zum Rückruf hinzuzufügen, wird eine Fehlermeldung angezeigt, die besagt:

React Hook "useContext" kann nicht innerhalb eines Rückrufs aufgerufen werden. React Hooks müssen in einer React-Funktionskomponente oder einer benutzerdefinierten React Hook-Funktion aufgerufen werden

NÄCHSTER VERSUCH

Ich habe meine Firebase-Konfigurationsdatei auf nur Konfigurationsvariablen reduziert (ich schreibe Helfer in die Kontextanbieter für jeden Kontext, den ich verwenden möchte).

import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

const devConfig = {
    apiKey: process.env.REACT_APP_DEV_API_KEY,
    authDomain: process.env.REACT_APP_DEV_AUTH_DOMAIN,
    databaseURL: process.env.REACT_APP_DEV_DATABASE_URL,
    projectId: process.env.REACT_APP_DEV_PROJECT_ID,
    storageBucket: process.env.REACT_APP_DEV_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_DEV_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_DEV_APP_ID

  };


  const prodConfig = {
    apiKey: process.env.REACT_APP_PROD_API_KEY,
    authDomain: process.env.REACT_APP_PROD_AUTH_DOMAIN,
    databaseURL: process.env.REACT_APP_PROD_DATABASE_URL,
    projectId: process.env.REACT_APP_PROD_PROJECT_ID,
    storageBucket: process.env.REACT_APP_PROD_STORAGE_BUCKET,
    messagingSenderId: 
process.env.REACT_APP_PROD_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_PROD_APP_ID
  };

  const config =
    process.env.NODE_ENV === 'production' ? prodConfig : devConfig;


class Firebase {
  constructor() {
    firebase.initializeApp(config);
    this.firebase = firebase;
    this.firestore = firebase.firestore();
    this.auth = firebase.auth();
  }
};

export default Firebase;  

Ich habe dann einen Auth-Kontextanbieter wie folgt:

import React, { useState, useEffect, useContext, createContext } from "react";
import Firebase from "../firebase";

const authContext = createContext();

// Provider component that wraps app and makes auth object ...
// ... available to any child component that calls useAuth().
export function ProvideAuth({ children }) {
  const auth = useProvideAuth();

  return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}

// Hook for child components to get the auth object ...
// ... and update when it changes.
export const useAuth = () => {

  return useContext(authContext);
};

// Provider hook that creates auth object and handles state
function useProvideAuth() {
  const [user, setUser] = useState(null);


  const signup = (email, password) => {
    return Firebase
      .auth()
      .createUserWithEmailAndPassword(email, password)
      .then(response => {
        setUser(response.user);
        return response.user;
      });
  };

  const signin = (email, password) => {
    return Firebase
      .auth()
      .signInWithEmailAndPassword(email, password)
      .then(response => {
        setUser(response.user);
        return response.user;
      });
  };



  const signout = () => {
    return Firebase
      .auth()
      .signOut()
      .then(() => {
        setUser(false);
      });
  };

  const sendPasswordResetEmail = email => {
    return Firebase
      .auth()
      .sendPasswordResetEmail(email)
      .then(() => {
        return true;
      });
  };

  const confirmPasswordReset = (password, code) => {
    // Get code from query string object
    const resetCode = code || getFromQueryString("oobCode");

    return Firebase
      .auth()
      .confirmPasswordReset(resetCode, password)
      .then(() => {
        return true;
      });
  };

  // Subscribe to user on mount
  useEffect(() => {

    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      if (user) {
        setUser(user);
      } else {
        setUser(false);
      }
    });

    // Subscription unsubscribe function
    return () => unsubscribe();
  }, []);

  return {
    user,
    signup,
    signin,
    signout,
    sendPasswordResetEmail,
    confirmPasswordReset
  };
}

const getFromQueryString = key => {
  return queryString.parse(window.location.search)[key];
};

Ich habe auch einen Firebase-Kontextanbieter wie folgt erstellt:

import React, { createContext } from 'react';
import Firebase from "../../firebase";

const FirebaseContext = createContext(null)
export { FirebaseContext }


export default ({ children }) => {

    return (
      <FirebaseContext.Provider value={ Firebase }>
        { children }
      </FirebaseContext.Provider>
    )
  }

Dann verpacke ich in index.js die App in den Firebase-Anbieter

ReactDom.render(
    <FirebaseProvider>
        <App />
    </FirebaseProvider>, 
document.getElementById("root"));

serviceWorker.unregister();

und in meiner Routenliste habe ich die relevanten Routen in den Authentifizierungsanbieter eingeschlossen:

import React from "react";
import IndexPage from "./index";
import { Switch, Route, Router } from "./../util/router.js";

import { ProvideAuth } from "./../util/auth.js";

function App(props) {
  return (
    <ProvideAuth>
      <Router>
        <Switch>
          <Route exact path="/" component={IndexPage} />

          <Route
            component={({ location }) => {
              return (
                <div
                  style={{
                    padding: "50px",
                    width: "100%",
                    textAlign: "center"
                  }}
                >
                  The page <code>{location.pathname}</code> could not be found.
                </div>
              );
            }}
          />
        </Switch>
      </Router>
    </ProvideAuth>
  );
}

export default App;

Bei diesem speziellen Versuch kehre ich zu dem Problem zurück, das zuvor mit diesem Fehler gekennzeichnet wurde:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth ist keine Funktion

Es verweist auf diese Zeile des Authentifizierungsanbieters, die das Problem verursacht:

useEffect(() => {

    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      if (user) {
        setUser(user);
      } else {
        setUser(false);
      }
    });

Ich habe versucht, in Firebase großgeschriebenes F zu verwenden, und es wird der gleiche Fehler generiert.

Wenn ich Tristans Rat versuche, entferne ich all diese Dinge und versuche, meine Abmeldemethode als Nichtauflistungsmethode zu definieren (ich weiß nicht, warum er nicht die Firebase-Sprache verwendet - aber wenn sein Ansatz funktioniert, würde ich mich mehr anstrengen um herauszufinden warum). Wenn ich versuche, seine Lösung zu verwenden, lautet die Fehlermeldung:

TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ default (...) ist keine Funktion

Die Antwort auf diesen Beitrag schlägt vor, () nach der Authentifizierung zu entfernen. Wenn ich das versuche, erhalte ich eine Fehlermeldung:

TypeError: Die Eigenschaft 'onAuthStateChanged' von undefined kann nicht gelesen werden

Dieser Beitrag weist jedoch auf ein Problem mit der Art und Weise hin, wie Firebase in die Auth-Datei importiert wird.

Ich habe es importiert als: importiere Firebase von "../firebase";

Firebase ist der Name der Klasse.

Die von Tristan empfohlenen Videos sind hilfreiche Hintergrundinformationen, aber ich bin derzeit in Episode 9 und habe immer noch nicht den Teil gefunden, der zur Lösung dieses Problems beitragen soll. Weiß jemand, wo man das findet?

NÄCHSTER VERSUCH Als nächstes - und nur um das Kontextproblem zu lösen - habe ich sowohl createContext als auch useContext importiert und versucht, sie wie in dieser Dokumentation gezeigt zu verwenden.

Ich kann keinen Fehler erhalten, der besagt:

Fehler: Ungültiger Hook-Aufruf. Hooks können nur innerhalb des Körpers einer Funktionskomponente aufgerufen werden. Dies kann aus einem der folgenden Gründe geschehen: ...

Ich habe die Vorschläge in diesem Link durchgesehen, um dieses Problem zu lösen, und kann es nicht herausfinden. Ich habe keine der in dieser Anleitung zur Fehlerbehebung aufgeführten Probleme.

Derzeit sieht die Kontextanweisung wie folgt aus:

import React, {  useContext } from 'react';
import Firebase from "../../firebase";


  export const FirebaseContext = React.createContext();

  export const useFirebase = useContext(FirebaseContext);

  export const FirebaseProvider = props => (
    <FirebaseContext.Provider value={new Firebase()}>
      {props.children}
    </FirebaseContext.Provider>
  );  

Ich habe Zeit damit verbracht, diesen udemy-Kurs zu verwenden, um zu versuchen, den Kontext und das Hook- Element für dieses Problem herauszufinden. Nach dem Betrachten ist der einzige Aspekt der von Tristan unten vorgeschlagenen Lösung, dass die createContext-Methode in seinem Beitrag nicht korrekt aufgerufen wird. Es muss "React.createContext" sein, aber es kommt der Lösung des Problems immer noch nicht nahe.

Ich stecke immer noch fest.

Kann jemand sehen, was hier schief gelaufen ist?


Es ist undefiniert, weil Sie es nicht importieren.
Josh Pittman

3
Sie brauchen nur hinzufügen exportzu export const FirebaseContext?
Federkun

Hallo Mel, entschuldige dich für die langsame Antwort. Ich hatte zwei verrückte Wochen in der Arbeit, also kam es nicht in Frage, abends auf einen Computer zu schauen! Ich habe meine Antwort für Sie aktualisiert, um eine saubere und sehr einfache Lösung bereitzustellen, die Sie überprüfen können.
Tristan Trainer

Vielen Dank - ich werde es mir jetzt ansehen.
Mel

Hey Mel, habe gerade mit der korrekten Implementierung der Echtzeit-Updates aus dem Firestore weiter aktualisiert (kann den onSnapshot-Teil entfernen, damit er nicht in Echtzeit angezeigt wird). Wenn dies die Lösung ist, kann ich vorschlagen, Ihre Frage möglicherweise zu aktualisieren, um sie zu einem zu machen viel kürzer und prägnanter, so dass andere, die es ansehen, möglicherweise auch die Lösung finden, danke - nochmals entschuldigen Sie die langsame Art der Antworten
Tristan Trainer

Antworten:


12

Hauptbearbeitung : Es hat einige Zeit gedauert, dies etwas genauer zu untersuchen. Ich habe mir eine sauberere Lösung ausgedacht. Jemand könnte mir nicht zustimmen, dass dies ein guter Weg ist, dies zu erreichen.

UseFirebase Auth Hook

import { useEffect, useState, useCallback } from 'react';
import firebase from 'firebase/app';
import 'firebase/auth';

const firebaseConfig = {
  apiKey: "xxxxxxxxxxxxxx",
  authDomain: "xxxx.firebaseapp.com",
  databaseURL: "https://xxxx.firebaseio.com",
  projectId: "xxxx",
  storageBucket: "xxxx.appspot.com",
  messagingSenderId: "xxxxxxxx",
  appId: "1:xxxxxxxxxx:web:xxxxxxxxx"
};

firebase.initializeApp(firebaseConfig)

const useFirebase = () => {
  const [authUser, setAuthUser] = useState(firebase.auth().currentUser);

  useEffect(() => {
    const unsubscribe = firebase.auth()
      .onAuthStateChanged((user) => setAuthUser(user))
    return () => {
      unsubscribe()
    };
  }, []);

  const login = useCallback((email, password) => firebase.auth()
    .signInWithEmailAndPassword(email, password), []);

  const logout = useCallback(() => firebase.auth().signOut(), [])

  return { login, authUser, logout }
}

export { useFirebase }

Wenn authUser null ist, wird er nicht authentifiziert. Wenn der Benutzer einen Wert hat, wird er authentifiziert.

firebaseConfigfinden Sie in der Firebase- Konsole => Projekteinstellungen => Apps => Optionsfeld Konfigurieren

useEffect(() => {
  const unsubscribe = firebase.auth()
    .onAuthStateChanged(setAuthUser)

  return () => {
    unsubscribe()
  };
}, []);

Dieser useEffect-Hook ist der Kern für die Verfolgung der authChanges eines Benutzers. Wir fügen dem onAuthStateChanged-Ereignis von firebase.auth () einen Listener hinzu, der den Wert von authUser aktualisiert. Diese Methode gibt einen Rückruf zum Abbestellen dieses Listeners zurück, mit dem wir den Listener bereinigen können, wenn der useFirebase-Hook aktualisiert wird.

Dies ist der einzige Hook, den wir für die Firebase-Authentifizierung benötigen (andere Hooks können für den Firestore usw. erstellt werden).

const App = () => {
  const { login, authUser, logout } = useFirebase();

  if (authUser) {
    return <div>
      <label>User is Authenticated</label>
      <button onClick={logout}>Logout</button>
    </div>
  }

  const handleLogin = () => {
    login("name@email.com", "password0");
  }

  return <div>
    <label>User is not Authenticated</label>
    <button onClick={handleLogin}>Log In</button>
  </div>
}

Dies ist eine grundlegende Implementierung der AppKomponente von acreate-react-app

useFirestore Database Hook

const useFirestore = () => {
  const getDocument = (documentPath, onUpdate) => {
    firebase.firestore()
      .doc(documentPath)
      .onSnapshot(onUpdate);
  }

  const saveDocument = (documentPath, document) => {
    firebase.firestore()
      .doc(documentPath)
      .set(document);
  }

  const getCollection = (collectionPath, onUpdate) => {
    firebase.firestore()
      .collection(collectionPath)
      .onSnapshot(onUpdate);
  }

  const saveCollection = (collectionPath, collection) => {
    firebase.firestore()
      .collection(collectionPath)
      .set(collection)
  }

  return { getDocument, saveDocument, getCollection, saveCollection }
}

Dies kann in Ihrer Komponente folgendermaßen implementiert werden:

const firestore = useFirestore();
const [document, setDocument] = useState();

const handleGet = () => {
  firestore.getDocument(
    "Test/ItsWFgksrBvsDbx1ttTf", 
    (result) => setDocument(result.data())
  );
}

const handleSave = () => {
  firestore.saveDocument(
    "Test/ItsWFgksrBvsDbx1ttTf", 
    { ...document, newField: "Hi there" }
  );

}}

Dadurch entfällt die Notwendigkeit für React useContext, da Updates direkt von Firebase selbst abgerufen werden.

Beachten Sie einige Dinge:

  1. Das Speichern eines unveränderten Dokuments löst keinen neuen Schnappschuss aus, sodass "Überspeichern" keine erneuten Rendern verursacht
  2. Beim Aufrufen von getDocument wird der Rückruf onUpdate sofort mit einem anfänglichen "Snapshot" aufgerufen, sodass Sie keinen zusätzlichen Code benötigen, um den Anfangszustand des Dokuments abzurufen.

Bearbeiten hat einen großen Teil der alten Antwort entfernt


1
Danke dafür. Ich kann nicht sehen, wie Sie das eingerichtet haben. Beim Anbieter wird eine Fehlermeldung angezeigt, dass createContext nicht definiert ist. Das liegt daran, dass es bisher keinen Verbraucher gibt. Wo hast du deine hingelegt?
Mel

Hey, sorry, createContext ist Teil von reag, also importiere es oben als {createContext} von 'react'. Mir wurde klar, dass ich vergessen habe zu zeigen, wohin der Firebase-Anbieter geht. Ich werde die Antwort bearbeiten
Tristan Trainer

Ich habe es in den Provider importiert, aber es wird undefiniert. Ich denke, das liegt daran, dass es keinen Verbraucher dafür gibt
Mel

1
Der Verbraucher ist der useContext () - Hook, aber wenn Sie Ihre Frage noch einmal betrachten, sieht es so aus, als würden Sie FirebaseContext nicht aus der Datei exportieren - deshalb kann er den Kontext nicht finden :)
Tristan Trainer

1
Hallo @ Mel danke, das ist sehr nett, ich hoffe es erweist sich am Ende als hilfreich. Hooks und Firebase sind beide ziemlich involviert und es hat lange gedauert, bis ich mich damit beschäftigt habe. Vielleicht habe ich selbst jetzt noch nicht die beste Lösung gefunden. Vielleicht erstelle ich irgendwann ein Tutorial zu meinem Ansatz, da es einfacher ist, es beim Codieren zu erklären es.
Tristan Trainer

4

Firebase ist undefiniert, da Sie es nicht importieren. Erstens muss es firebase.firestore()wie im Beispiel in den Dokumenten gezeigt sein, die Sie mit https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore verlinkt haben . Dann müssen Sie tatsächlich Firebase in die Datei importieren, import * as firebase from 'firebase';wie in der Readme- Datei https://www.npmjs.com/package/firebase des Firebase-Pakets beschrieben


1
Ich importiere es in index.js
Mel

1
ReactDOM.render (<FirebaseContext.Provider value = {new Firebase ()}> <App /> </FirebaseContext.Provider>, document.getElementById ('root'));
Mel

1
Deshalb funktioniert der Ansatz mit componentDidMount
Mel

1
Das withAuth-HOC ist auch in withFirebase eingeschlossen.
Mel

3
Aber Ihr Fehler sagt, dass es undefiniert ist. Ich helfe Ihnen bei der Definition und Sie sagen mir nicht, ob die Lösung funktioniert oder was der resultierende Fehler ist. Du machst es dir immer sehr schwer, dir zu helfen, Mel. Mein Punkt ist, dass Sie es in eine andere Datei als die Dashboard2-Komponentendatei importieren, auf die Sie verweisen, was den Fehler verursacht. Wenn Sie etwas in den Index importieren, kann Ihr Build nicht verstehen, was es in einer völlig anderen Datei enthält.
Josh Pittman

2

EDIT (3. März 2020):

Beginnen wir von vorne.

  1. Ich habe ein neues Projekt erstellt:

    Garn erstellen React-App Firebase-Hook-Problem

  2. Ich habe alle 3 standardmäßig erstellten App * -Dateien gelöscht, den Import aus index.js entfernt und auch den Service Worker entfernt, um eine saubere index.js wie diese zu erhalten:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

const App = () => {
    return (
        <div>
            Hello Firebase!            
        </div>
    );
};

ReactDOM.render(<App />, document.getElementById('root'));
  1. Ich habe die App gestartet, um zu sehen, dass Hello Firebase! wird ausgedruckt.
  2. Ich habe ein Firebase-Modul hinzugefügt
yarn add firebase
  1. Ich habe firebase init ausgeführt, um firebase für dieses Projekt einzurichten. Ich habe eines meiner leeren Firebase-Projekte ausgewählt und Datenbank und Firestore ausgewählt, um die nächsten Dateien zu erstellen:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. Ich habe Importe für Firebase-Bibliotheken hinzugefügt und auch eine Firebase- Klasse und FirebaseContext erstellt . Zuletzt habe ich die App in die FirebaseContext.Provider- Komponente eingeschlossen und ihren Wert auf eine neue Firebase () -Instanz festgelegt. Dies war, wir werden nur eine Instanz der Firebase-App instanziieren lassen, wie wir sollten, weil es ein Singleton sein muss:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";

import app from "firebase/app";
import "firebase/database";
import "firebase/auth";
import "firebase/firestore";

class Firebase {
    constructor() {
        app.initializeApp(firebaseConfig);

        this.realtimedb = app.database();
        this.firestore = app.firestore();
    }
}

const FirebaseContext = React.createContext(null);

const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
};

const App = () => {
    return <div>Hello Firebase!</div>;
};

ReactDOM.render(
    <FirebaseContext.Provider value={new Firebase()}>
        <App />
    </FirebaseContext.Provider>
    , document.getElementById("root"));
  1. Lassen Sie uns überprüfen, ob wir alles aus dem Firestore lesen können. Um nur den Messwert zuerst zu überprüfen, ging ich zu meinem Projekt in der Firebase-Konsole, öffnete meine Cloud Firestore-Datenbank und fügte eine neue Sammlung mit dem Namen " Zähler" hinzu. Ein einfaches Dokument enthielt ein Feld mit dem Namen " Wert vom Typ" Nummer "und" Wert 0 ". Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

  2. Dann habe ich die App-Klasse aktualisiert, um den von uns erstellten FirebaseContext zu verwenden, den useState-Hook für unseren einfachen Counter-Hook erstellt und den useEffect-Hook verwendet, um den Wert aus dem Firestore zu lesen:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";

import app from "firebase/app";
import "firebase/database";
import "firebase/auth";
import "firebase/firestore";

const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
};

class Firebase {
    constructor() {
        app.initializeApp(firebaseConfig);

        this.realtimedb = app.database();
        this.firestore = app.firestore();
    }
}

const FirebaseContext = React.createContext(null);

const App = () => {
    const firebase = React.useContext(FirebaseContext);
    const [counter, setCounter] = React.useState(-1);

    React.useEffect(() => {
        firebase.firestore.collection("counters").doc("simple").get().then(doc => {
            if(doc.exists) {
                const data = doc.data();
                setCounter(data.value);
            } else {
                console.log("No such document");
            }
        }).catch(e => console.error(e));
    }, []);

    return <div>Current counter value: {counter}</div>;
};

ReactDOM.render(
    <FirebaseContext.Provider value={new Firebase()}>
        <App />
    </FirebaseContext.Provider>
    , document.getElementById("root"));

Hinweis: Um die Antwort so kurz wie möglich zu halten, habe ich sichergestellt, dass Sie nicht mit firebase authentifiziert werden müssen, indem ich den Zugriff auf den Firestore so eingestellt habe, dass er sich im Testmodus befindet (Datei firestore.rules):

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // This rule allows anyone on the internet to view, edit, and delete
    // all data in your Firestore database. It is useful for getting
    // started, but it is configured to expire after 30 days because it
    // leaves your app open to attackers. At that time, all client
    // requests to your Firestore database will be denied.
    //
    // Make sure to write security rules for your app before that time, or else
    // your app will lose access to your Firestore database
    match /{document=**} {
      allow read, write: if request.time < timestamp.date(2020, 4, 8);
    }
  }
}

Meine vorherige Antwort: Gerne können Sie sich mein React-Firebase-Auth-Skelett ansehen:

https://github.com/PompolutZ/react-firebase-auth-skeleton

Es folgt hauptsächlich dem Artikel:

https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial

Aber etwas umgeschrieben, um Hooks zu verwenden. Ich habe es in mindestens zwei meiner Projekte verwendet.

Typische Verwendung aus meinem aktuellen Haustierprojekt:

import React, { useState, useEffect, useContext } from "react";
import ButtonBase from "@material-ui/core/ButtonBase";
import Typography from "@material-ui/core/Typography";
import DeleteIcon from "@material-ui/icons/Delete";
import { FirebaseContext } from "../../../firebase";
import { useAuthUser } from "../../../components/Session";
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles(theme => ({
    root: {
        flexGrow: 1,
        position: "relative",
        "&::-webkit-scrollbar-thumb": {
            width: "10px",
            height: "10px",
        },
    },

    itemsContainer: {
        position: "absolute",
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        display: "flex",
        alignItems: "center",
        overflow: "auto",
    },
}));

export default function LethalHexesPile({
    roomId,
    tokens,
    onSelectedTokenChange,
}) {
    const classes = useStyles();
    const myself = useAuthUser();
    const firebase = useContext(FirebaseContext);
    const pointyTokenBaseWidth = 95;
    const [selectedToken, setSelectedToken] = useState(null);

    const handleTokenClick = token => () => {
        setSelectedToken(token);
        onSelectedTokenChange(token);
    };

    useEffect(() => {
        console.log("LethalHexesPile.OnUpdated", selectedToken);
    }, [selectedToken]);

    const handleRemoveFromBoard = token => e => {
        console.log("Request remove token", token);
        e.preventDefault();
        firebase.updateBoardProperty(roomId, `board.tokens.${token.id}`, {
            ...token,
            isOnBoard: false,
            left: 0,
            top: 0,
            onBoard: { x: -1, y: -1 },
        });
        firebase.addGenericMessage2(roomId, {
            author: "Katophrane",
            type: "INFO",
            subtype: "PLACEMENT",
            value: `${myself.username} removed lethal hex token from the board.`,
        });
    };

    return (
        <div className={classes.root}>
            <div className={classes.itemsContainer}>
                {tokens.map(token => (
                    <div
                        key={token.id}
                        style={{
                            marginRight: "1rem",
                            paddingTop: "1rem",
                            paddingLeft: "1rem",
                            filter:
                            selectedToken &&
                            selectedToken.id === token.id
                                ? "drop-shadow(0 0 10px magenta)"
                                : "",
                            transition: "all .175s ease-out",
                        }}
                        onClick={handleTokenClick(token)}
                    >
                        <div
                            style={{
                                width: pointyTokenBaseWidth * 0.7,
                                position: "relative",
                            }}
                        >
                            <img
                                src={`/assets/tokens/lethal.png`}
                                style={{ width: "100%" }}
                            />
                            {selectedToken && selectedToken.id === token.id && (
                                <ButtonBase
                                    style={{
                                        position: "absolute",
                                        bottom: "0%",
                                        right: "0%",
                                        backgroundColor: "red",
                                        color: "white",
                                        width: "2rem",
                                        height: "2rem",
                                        borderRadius: "1.5rem",
                                        boxSizing: "border-box",
                                        border: "2px solid white",
                                    }}
                                    onClick={handleRemoveFromBoard(token)}
                                >
                                    <DeleteIcon
                                        style={{
                                            width: "1rem",
                                            height: "1rem",
                                        }}
                                    />
                                </ButtonBase>
                            )}
                        </div>
                        <Typography>{`${token.id}`}</Typography>
                    </div>
                ))}
            </div>
        </div>
    );
}

Zwei wichtige Teile hier sind: - useAuthUser () -Hook, der den aktuellen authentifizierten Benutzer bereitstellt. - FirebaseContext, den ich über den useContext- Hook verwende.

const firebase = useContext(FirebaseContext);

Wenn Sie einen Kontext zu firebase haben, liegt es an Ihnen, das firebase-Objekt nach Ihren Wünschen zu implementieren. Manchmal schreibe ich einige hilfreiche Funktionen, manchmal ist es einfacher, Listener direkt im useEffect- Hook einzurichten , den ich für meine aktuelle Komponente erstelle.

Einer der besten Teile dieses Artikels war die Erstellung von withAuthorization HOC, mit der Sie die Voraussetzungen für den Zugriff auf die Seite entweder in der Komponente selbst angeben können:

const condition = authUser => authUser && !!authUser.roles[ROLES.ADMIN];
export default withAuthorization(condition)(AdminPage);

Oder stellen Sie diese Bedingungen sogar direkt in Ihrer Router-Implementierung ein.

Ich hoffe, dass Ihnen das Betrachten des Repos und des Artikels einige besonders gute Gedanken gibt, um andere brillante Antworten auf Ihre Frage zu verbessern.


Ich kaufte sein Buch und folgte seinem Ansatz. Ich stellte fest, dass der Bedingungsansatz bei der Implementierung nicht funktionierte und dass das in diesem Buch beschriebene Authentifizierungsprotokoll den Status durch Komponentenaktualisierungen nicht aufrechterhalten konnte. Ich habe keinen Weg gefunden, das zu verwenden, was in diesem Buch beschrieben ist. Trotzdem danke, dass du deine Gedanken geteilt hast.
Mel

Ich bin mir nicht sicher, was du meinst. Haben Sie meine Skeleton-App mit Ihrem Firebase-Projekt ausprobiert? Soweit ich das beurteilen kann, funktionieren alle Bedingungen, da ich sie in mindestens 3 Projekten verwendet habe.
fxdxpz
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.