Ascoltatore Firebase con React Hooks


27

Sto cercando di capire come utilizzare un listener Firebase in modo che i dati del cloud store vengano aggiornati con gli aggiornamenti degli hook di reazione.

Inizialmente, l'ho realizzato utilizzando un componente di classe con una funzione componentDidMount per ottenere i dati del deposito.

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

Ciò si interrompe quando la pagina si aggiorna, quindi sto cercando di capire come spostare l'ascoltatore per reagire agli hook.

Ho installato i reattivi firebase-hook strumento - anche se non riesco a capire come leggere le istruzioni per riuscire a farlo funzionare.

Ho un componente di funzione come segue:

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

Questo componente è racchiuso in un wrapper authUser a livello di route come segue:

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

Ho un file firebase.js, che si collega al fuoco come segue:

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


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


  }

Definisce inoltre un ascoltatore per sapere quando cambia l'autenticatore:

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

Quindi, nella mia configurazione del contesto Firebase ho:

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

Il contesto è configurato in un wrapper withFirebase come segue:

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;

Quindi, nel mio HOC withAuthentication, ho un provider di contesto come:

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;

Attualmente - quando provo questo, ricevo un errore nel componente Dashboard2 che dice:

Firebase 'non è definito

Ho provato la base di fuoco in minuscolo e ho ottenuto lo stesso errore.

Ho anche provato firebase.firestore e Firebase.firestore. Ho fatto lo stesso errore.

Mi chiedo se non riesco a utilizzare il mio HOC con un componente di funzione?

Ho visto questa app demo e questo post sul blog .

Seguendo i consigli nel blog, ho realizzato un nuovo firebase / contextReader.jsx con:

 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
  }  

Quindi provo ad avvolgere il mio App.jsx in quel lettore con:

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

Quando provo questo, ricevo un errore che dice:

TipoErrore: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth non è una funzione

Ho visto questo post affrontare quell'errore e ho provato a disinstallare e reinstallare filato. Non fa differenza.

Quando guardo l' app demo , mi viene in mente che il contesto dovrebbe essere creato usando un metodo "interfaccia". Non riesco a vedere da dove provenga, non riesco a trovare un riferimento per spiegarlo nella documentazione.

Non riesco a dare un senso alle istruzioni oltre a provare quello che ho fatto per collegarlo.

Ho visto questo post che tenta di ascoltare il camino senza usare i ganci di reazione-fuoco. Le risposte rimandano al tentativo di capire come usare questo strumento.

Ho letto questa eccellente spiegazione che spiega come spostarsi dai HOC ai ganci. Sono bloccato con come integrare l'ascoltatore base di fuoco.

Ho visto questo post che fornisce un utile esempio su come pensare di farlo. Non sono sicuro se dovrei provare a farlo nel componente authListenerDidMount - o nel componente Dashboard che sta cercando di usarlo.

PROSSIMO TENTATIVO Ho trovato questo post , che sta cercando di risolvere lo stesso problema.

Quando provo a implementare la soluzione offerta da Shubham Khatri, ho impostato la configurazione della base di fuoco come segue:

Un provider di contesto con: import React, {useContext} da 'reagire'; importare Firebase da '../../firebase';

const FirebaseContext = React.createContext(); 

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

L'hook di contesto ha quindi:

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;

Quindi in index.js avvolgo l'app nel provider come:

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

Quindi, quando provo ad usare l'ascoltatore nel componente ho:

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;

E provo ad usarlo come percorso senza componenti o wrapper di autenticazione:

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

Quando provo questo, ricevo un errore che dice:

Tentativo di errore di importazione: "FirebaseContext" non viene esportato da "../Firebase/ContextHookProvider".

Questo messaggio di errore ha senso, poiché ContextHookProvider non esporta FirebaseContext - esporta FirebaseProvider - ma se non provo a importarlo in Dashboard2 - non posso accedervi nella funzione che tenta di utilizzarlo.

Un effetto collaterale di questo tentativo è che il mio metodo di iscrizione non funziona più. Ora genera un messaggio di errore che dice:

TypeError: impossibile leggere la proprietà 'doCreateUserWithEmailAndPassword' di null

Risolverò questo problema in seguito, ma ci deve essere un modo per capire come usare la reazione con firebase che non implichi mesi di questo ciclo attraverso milioni di strade che non funzionano per ottenere un'installazione di base di autorizzazione. Esiste uno starter kit per firebase (fireestore) che funziona con ganci reattivi?

Il prossimo tentativo ho provato a seguire l'approccio in questo corso udemy - ma funziona solo per generare un input di modulo - non c'è un ascoltatore che metta in giro le rotte per adattarsi all'utente autenticato.

Ho provato a seguire l'approccio in questo tutorial di YouTube - che ha questo repository su cui lavorare. Mostra come usare gli hook, ma non come usare il contesto.

PROSSIMO TENTATIVO Ho trovato questo repository che sembra avere un approccio ben ponderato all'utilizzo dei ganci con il camino. Tuttavia, non riesco a dare un senso al codice.

Ho clonato questo - e ho provato ad aggiungere tutti i file pubblici e poi quando lo eseguo - non riesco davvero a far funzionare il codice. Non sono sicuro di cosa manchi dalle istruzioni su come farlo funzionare per vedere se ci sono lezioni nel codice che possono aiutare a risolvere questo problema.

PROSSIMO TENTATIVO

Ho acquistato il modello divjoy, che è pubblicizzato come impostato per firebase (non è impostato per il negozio di alimentari nel caso in cui qualcun altro lo stia considerando come un'opzione).

Tale modello propone un wrapper di autenticazione che inizializza la configurazione dell'app - ma solo per i metodi di autenticazione - quindi deve essere ristrutturato per consentire a un altro provider di contesto per il deposito. Quando confondi quel processo e utilizzi il processo mostrato in questo post , ciò che rimane è un errore nel seguente callback:

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

Non sa cos'è la base di fuoco. Questo perché è definito nel provider di contesto Firebase che viene importato e definito (nella funzione useProvideAuth ()) come:

  const firebase = useContext(FirebaseContext)

Senza possibilità di richiamata, l'errore dice:

React Hook useEffect ha una dipendenza mancante: 'firebase'. Includerlo o rimuovere l'array di dipendenze

Oppure, se provo ad aggiungere quella const al callback, ricevo un errore che dice:

React Hook "useContext" non può essere chiamato all'interno di un callback. I React Hook devono essere chiamati in un componente della funzione React o in una funzione React Hook personalizzata

PROSSIMO TENTATIVO

Ho ridotto il mio file di configurazione firebase solo a variabili di configurazione (scriverò helper nei provider di contesto per ogni contesto che voglio usare).

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;  

Ho quindi un provider di contesto di autenticazione come segue:

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

Ho anche creato un provider di contesto Firebase come segue:

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

Quindi, in index.js avvolgo l'app nel provider di Firebase

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

serviceWorker.unregister();

e nel mio elenco di rotte, ho spostato le rotte pertinenti nel provider di autenticazione:

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;

In questo particolare tentativo, sono tornato al problema segnalato in precedenza con questo errore:

TipoErrore: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth non è una funzione

Indica questa riga del provider di autenticazione come la creazione del problema:

useEffect(() => {

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

Ho provato a usare la maiuscola F in Firebase e genera lo stesso errore.

Quando provo il consiglio di Tristan, rimuovo tutte quelle cose e provo a definire il mio metodo di annullamento dell'iscrizione come metodo non ascoltato (non so perché non stia usando il linguaggio firebase - ma se il suo approccio funzionasse, proverei di più per capire perché). Quando provo ad usare la sua soluzione, il messaggio di errore dice:

TipoErrore: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ default (...) non è una funzione

La risposta a questo post suggerisce di rimuovere () da after auth. Quando provo quello, ottengo un errore che dice:

TypeError: impossibile leggere la proprietà 'onAuthStateChanged' di undefined

Tuttavia, questo post suggerisce un problema con il modo in cui firebase viene importato nel file auth.

L'ho importato come: import Firebase da "../firebase";

Firebase è il nome della classe.

I video consigliati da Tristan sono utili, ma al momento sono sull'episodio 9 e non ho ancora trovato la parte che dovrebbe aiutare a risolvere questo problema. Qualcuno sa dove trovarlo?

PROSSIMO TENTATIVO Avanti - e cercando di risolvere solo il problema di contesto - Ho importato sia createContext che useContext e ho provato a usarli come mostrato in questa documentazione.

Non riesco a farmi passare un errore che dice:

Errore: chiamata hook non valida. Gli hook possono essere chiamati solo all'interno del corpo di un componente di funzione. Questo potrebbe accadere per uno dei seguenti motivi: ...

Ho seguito i suggerimenti in questo link per provare a risolvere questo problema e non riesco a capirlo. Non ho riscontrato nessuno dei problemi indicati in questa guida alla risoluzione dei problemi.

Attualmente - la dichiarazione di contesto ha il seguente aspetto:

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

Ho trascorso del tempo usando questo corso udemy per cercare di capire il contesto e agganciare l'elemento a questo problema - dopo averlo visto - l'unico aspetto della soluzione proposta da Tristan di seguito è che il metodo createContext non è chiamato correttamente nel suo post. deve essere "React.createContext" ma non si avvicina ancora per risolvere il problema.

Sono ancora bloccato.

Qualcuno può vedere cosa è andato storto qui?


Non è definito perché non lo stai importando.
Josh Pittman,

3
ti basta aggiungere exporta export const FirebaseContext?
Federkun,

Ciao Mel, mi scuso per la risposta lenta, ho avuto due settimane folli di lavoro, quindi guardare un computer la sera era fuori discussione! Ho aggiornato la mia risposta per fornirti una soluzione pulita e molto semplice da controllare.
Allenatore di Tristan,

Grazie mille - Lo guarderò ora.
Mel

Ehi Mel, appena aggiornato ulteriormente con la corretta implementazione degli aggiornamenti in tempo reale anche da Firestore (puoi rimuovere la parte onSnapshot per renderla non in tempo reale) Se questa è la soluzione, ti suggerisco di aggiornare eventualmente la tua domanda per renderla un molto più breve e più conciso, quindi altri che la visualizzano potrebbero trovare la soluzione, grazie - scusate ancora per la lentezza delle risposte
Tristan Trainer

Risposte:


12

Modifica importante : ci è voluto del tempo per approfondire un po 'di più questo è quello che ho trovato è una soluzione più pulita, qualcuno potrebbe non essere d'accordo con me sul fatto che questo sia un buon modo per affrontarlo.

Usa hook di autenticazione Firebase

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 }

Se authUser è null, allora non autenticato, se l'utente ha un valore, quindi autenticato.

firebaseConfigpuò essere trovato sulla Firebase Console => Impostazioni progetto => App => Pulsante di opzione Config

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

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

Questo hook useEffect è il core per tracciare i authChanges di un utente. Aggiungiamo un listener all'evento onAuthStateChanged di firebase.auth () che aggiorna il valore di authUser. Questo metodo restituisce un callback per annullare l'iscrizione a questo listener che possiamo usare per ripulire il listener quando viene aggiornato l'hook useFirebase.

Questo è l'unico hook di cui abbiamo bisogno per l'autenticazione firebase (altri hook possono essere realizzati per il fireestore ecc.

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

Questa è un'implementazione di base del Appcomponente di acreate-react-app

hook di database useFirestore

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

Questo può essere implementato nel tuo componente in questo modo:

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

}

Questo rimuove quindi la necessità di React useContext poiché otteniamo aggiornamenti direttamente dalla base di fuoco stessa.

Nota un paio di cose:

  1. Il salvataggio di un documento invariato non attiva una nuova istantanea, pertanto il "salvataggio eccessivo" non provoca ritrasferimenti
  2. Quando si chiama getDocument, il callback onUpdate viene chiamato immediatamente con uno "snapshot" iniziale, quindi non è necessario un codice aggiuntivo per ottenere lo stato iniziale del documento.

Modifica ha rimosso una grande porzione della vecchia risposta


1
grazie per questo. Non riesco a vedere come lo hai impostato. Con il provider, ricevo un errore che dice che createContext non è definito. Questo perché non c'è nessun consumatore finora. Dove hai messo il tuo?
Mel

Ehi, scusa, createContext fa parte di reazioni, quindi importalo in alto come {createContext} da 'reagisci' Mi sono reso conto di aver dimenticato di mostrare dove va il fornitore di Firebase, modificherò la risposta
Tristan Trainer

L'ho importato nel provider, ma viene reso indefinito. Penso che sia perché non c'è consumatore per questo
Mel

1
Il consumatore è l'hook useContext (), ma guardando di nuovo la tua domanda, sembra che tu non stia esportando FirebaseContext dal file - ecco perché non riesce a trovare il contesto :)
Tristan Trainer

1
Ciao @Mel, grazie, è molto gentile, spero che alla fine si riveli utile. Hooks e Firebase sono entrambi abbastanza coinvolti e mi ci è voluto molto tempo per capirlo e forse non ho trovato la soluzione migliore anche adesso, potrei creare un tutorial sul mio approccio un po 'di tempo poiché è più facile da spiegare mentre scrivi esso.
Allenatore Tristan del

4

Firebase non è definito perché non lo stai importando. Innanzitutto, deve essere firebase.firestore()come mostrato nell'esempio sui documenti collegati a https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore . Quindi devi effettivamente importare firebase nel file, quindi import * as firebase from 'firebase';come indicato nel pacchetto firebase readme https://www.npmjs.com/package/firebase


1
Lo sto importando in index.js
Mel

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

1
Ecco perché l'approccio funziona con componentDidMount
Mel

1
Anche withAuth HOC è incluso in withFirebase.
Mel

3
Ma il tuo errore dice che non è definito. Ti sto aiutando a definirlo e non mi stai dicendo se la soluzione funziona o qual è l'errore risultante. Rendi sempre molto difficile aiutarti Mel. Il punto è che lo stai importando in un file diverso dal file del componente dashboard2, che è dove lo fai riferimento, che sta causando l'errore. Immaginare qualcosa in indice non aiuta la tua build a capire cosa sia in un file completamente diverso.
Josh Pittman,

2

EDIT (3 marzo 2020):

Partiamo da zero.

  1. Ho creato un nuovo progetto:

    filato crea il problema di reazione-app firebase-hook

  2. Ho cancellato tutti e 3 i file App * creati per impostazione predefinita, rimosso l'importazione da index.js e rimosso anche il service worker per avere un index.js pulito come questo:

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. Ho avviato l'app per vedere Hello Firebase! è stampato.
  2. Ho aggiunto il modulo Firebase
yarn add firebase
  1. Ho eseguito Firebase Init per configurare Firebase per quel progetto. Ho scelto uno dei miei progetti Firebase vuoti e ho selezionato Database e Firestore, che finiscono per creare i file successivi:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. Ho aggiunto le importazioni per le librerie Firebase e ho anche creato una classe Firebase e FirebaseContext . Alla fine ho avvolto l'app nel componente FirebaseContext.Provider e impostato il suo valore su una nuova istanza di Firebase () . In questo caso avremo solo un'istanza dell'app Firebase creata come dovremmo perché deve essere un singleton:
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. Verifichiamo di poter leggere qualsiasi cosa da Firestore. Per verificare solo la lettura, sono andato al mio progetto in Firebase Console, ho aperto il mio database Cloud Firestore e ho aggiunto una nuova raccolta chiamata counter con un semplice documento contenente un campo chiamato value of type number e value 0. inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

  2. Quindi ho aggiornato la classe App per usare FirebaseContext che abbiamo creato, fatto uso hook di stato per il nostro semplice hook contatore e usato hook di useEffect per leggere il valore dal negozio:

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

Nota: per mantenere la risposta il più breve possibile, ho fatto in modo che non sia necessario autenticarsi con Firebase, impostando l'accesso al Firestore in modalità test (file 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);
    }
  }
}

La mia risposta precedente: sei il benvenuto per dare un'occhiata al mio scheletro di reazione-firebase-auth:

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

Segue principalmente l'articolo:

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

Ma in qualche modo riscritto per usare i ganci. L'ho usato in almeno due dei miei progetti.

Utilizzo tipico del mio attuale progetto per animali domestici:

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

Due parti più importanti qui sono: - hook useAuthUser () che fornisce all'utente autenticato corrente. - FirebaseContext che utilizzo tramite hook useContext .

const firebase = useContext(FirebaseContext);

Quando hai un contesto da Firebase, sta a te implementare l'oggetto Firebase a tuo piacimento. A volte scrivo alcune utili funzioni, a volte è più semplice configurare i listener direttamente nel gancio useEffect che creo per il mio componente corrente.

Una delle parti migliori di questo articolo è stata la creazione di withAuthorization HOC, che consente di specificare i prerequisiti per accedere alla pagina nel componente stesso:

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

O forse imposta queste condizioni direttamente nell'implementazione del tuo router.

Spero che guardando il repository e l'articolo ti dia qualche idea in più per migliorare altre brillanti risposte alla tua domanda.


Ho comprato il suo libro e ho seguito il suo approccio. Ho scoperto che l'approccio delle condizioni in realtà non funzionava quando implementato e che il protocollo di autenticazione indicato in quel libro non riusciva a mantenere lo stato attraverso gli aggiornamenti dei componenti. Non ho trovato il modo di usare ciò che è indicato in quel libro. Grazie comunque per aver condiviso i tuoi pensieri.
Mel

Non sono sicuro di cosa tu voglia dire. Hai provato la mia app skeleton con il tuo progetto Firebase? Tutte le condizioni funzionano per quanto posso dire, perché l'ho usato in almeno 3 progetti.
fxdxpz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.