Pendengar Firebase dengan React Hooks

27

Saya mencoba mencari cara menggunakan pendengar Firebase agar data cloud firestore di-refresh dengan pembaruan kait reaksi.

Awalnya, saya membuat ini menggunakan komponen kelas dengan fungsi componentDidMount untuk mendapatkan data firestore.

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

Itu rusak ketika halaman diperbarui, jadi saya mencoba mencari cara untuk memindahkan pendengar untuk bereaksi.

Saya telah menginstal alat react-firebase-hooks - walaupun saya tidak tahu cara membaca instruksi untuk membuatnya berfungsi.

Saya memiliki komponen fungsi sebagai berikut:

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

Komponen ini dibungkus pembungkus pengguna otomatis pada tingkat rute sebagai berikut:

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

Saya memiliki file firebase.js, yang dihubungkan ke firestore sebagai berikut:

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


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


  }

Itu juga mendefinisikan pendengar untuk mengetahui kapan authUser berubah:

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

Kemudian, dalam pengaturan konteks firebase saya, saya memiliki:

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

Konteksnya diatur dalam pembungkus withFirebase sebagai berikut:

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;

Kemudian, dalam HOC withAuthentication saya, saya memiliki penyedia konteks sebagai:

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;

Saat ini - ketika saya mencoba ini, saya mendapatkan kesalahan dalam komponen Dashboard2 yang mengatakan:

Firebase 'tidak ditentukan

Saya mencoba firebase huruf kecil dan mendapatkan kesalahan yang sama.

Saya juga mencoba firebase.firestore dan Firebase.firestore. Saya mendapatkan kesalahan yang sama.

Saya ingin tahu apakah saya tidak dapat menggunakan HOC saya dengan komponen fungsi?

Saya telah melihat aplikasi demo ini dan posting blog ini .

Mengikuti saran di blog, saya membuat firebase / contextReader.jsx baru dengan:

 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
  }  

Lalu saya mencoba untuk membungkus App.jsx di pembaca itu dengan:

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

Ketika saya mencoba ini, saya mendapatkan kesalahan yang mengatakan:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth bukan fungsi

Saya telah melihat posting ini berurusan dengan kesalahan itu dan telah mencoba mencopot dan menginstal ulang utas. Tidak ada bedanya.

Ketika saya melihat aplikasi demo , itu menunjukkan bahwa konteks harus dibuat menggunakan metode 'antarmuka'. Saya tidak bisa melihat dari mana ini berasal - saya tidak dapat menemukan referensi untuk menjelaskannya di dokumentasi.

Saya tidak dapat memahami instruksi selain mencoba apa yang telah saya lakukan untuk menyambungkan ini.

Saya telah melihat posting ini yang mencoba mendengarkan firestore tanpa menggunakan hook-react-firebase. Jawabannya mengarah pada upaya mencari tahu cara menggunakan alat ini.

Saya telah membaca penjelasan yang luar biasa ini yang membahas cara berpindah dari HOC ke kait. Saya terjebak dengan cara mengintegrasikan pendengar firebase.

Saya telah melihat posting ini yang memberikan contoh bermanfaat untuk bagaimana berpikir tentang melakukan ini. Tidak yakin apakah saya harus mencoba melakukan ini di authListener componentDidMount - atau di komponen Dashboard yang mencoba menggunakannya.

PERIKSA BERIKUTNYA Saya menemukan posting ini , yang mencoba untuk memecahkan masalah yang sama.

Ketika saya mencoba menerapkan solusi yang ditawarkan oleh Shubham Khatri, saya mengatur konfigurasi firebase sebagai berikut:

Penyedia konteks dengan: import React, {useContext} dari 'react'; impor Firebase dari '../../firebase';

const FirebaseContext = React.createContext(); 

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

Pengait konteks kemudian memiliki:

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;

Kemudian di index.js saya membungkus Aplikasi di penyedia sebagai:

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

Kemudian, ketika saya mencoba menggunakan pendengar dalam komponen yang saya miliki:

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;

Dan saya mencoba menggunakannya sebagai rute tanpa komponen atau pembungkus auth:

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

Ketika saya mencoba ini, saya mendapatkan kesalahan yang mengatakan:

Mencoba impor kesalahan: 'FirebaseContext' tidak diekspor dari '../Firebase/ContextHookProvider'.

Pesan kesalahan itu masuk akal, karena ContextHookProvider tidak mengekspor FirebaseContext - ini mengekspor FirebaseProvider - tetapi jika saya tidak mencoba mengimpor ini di Dashboard2 - maka saya tidak dapat mengaksesnya dalam fungsi yang mencoba menggunakannya.

Salah satu efek samping dari upaya ini adalah metode pendaftaran saya tidak lagi berfungsi. Sekarang menghasilkan pesan kesalahan yang mengatakan:

TypeError: Tidak dapat membaca properti 'doCreateUserWithEmailAndPassword' dari nol

Saya akan memecahkan masalah ini nanti- tetapi harus ada cara untuk mencari tahu bagaimana menggunakan bereaksi dengan firebase yang tidak melibatkan berbulan-bulan dari loop ini melalui jutaan jalan yang tidak bekerja untuk mendapatkan pengaturan auth dasar. Apakah ada starter kit untuk firebase (firestore) yang berfungsi dengan kait reaksi?

Upaya berikutnya saya mencoba mengikuti pendekatan dalam kursus udemy ini - tetapi hanya berfungsi untuk menghasilkan input formulir - tidak ada pendengar untuk menyiasati rute untuk menyesuaikan dengan pengguna yang diautentikasi.

Saya mencoba mengikuti pendekatan dalam tutorial youtube ini - yang membuat repo ini berfungsi. Ini menunjukkan cara menggunakan kait, tetapi tidak bagaimana menggunakan konteks.

PERIKSA BERIKUTNYA Saya menemukan repo ini yang tampaknya memiliki pendekatan yang dipikirkan dengan baik untuk menggunakan kait dengan firestore. Namun, saya tidak dapat memahami kode tersebut.

Saya mengkloning ini - dan mencoba untuk menambahkan semua file publik dan kemudian ketika saya menjalankannya - saya benar-benar tidak bisa mendapatkan kode untuk beroperasi. Saya tidak yakin apa yang tidak ada dalam instruksi untuk menjalankannya untuk melihat apakah ada pelajaran dalam kode yang dapat membantu menyelesaikan masalah ini.

PERIKSAAN BERIKUTNYA

Saya membeli template divjoy, yang diiklankan sebagai setup untuk firebase (ini bukan setup untuk firestore jika ada orang lain yang mempertimbangkan ini sebagai opsi).

Templat itu mengusulkan pembungkus autis yang menginisialisasi konfigurasi aplikasi - tetapi hanya untuk metode auth - sehingga perlu direstrukturisasi untuk memungkinkan penyedia konteks lain untuk firestore. Ketika Anda mengacaukan proses itu dan menggunakan proses yang ditunjukkan dalam posting ini , apa yang tersisa adalah kesalahan dalam panggilan balik berikut:

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

Tidak tahu apa itu firebase. Itu karena itu didefinisikan dalam penyedia konteks firebase yang diimpor dan didefinisikan (dalam fungsi useProvideAuth ()) sebagai:

  const firebase = useContext(FirebaseContext)

Tanpa peluang untuk callback, kesalahan mengatakan:

Bereaksi Hook useEffect memiliki ketergantungan yang hilang: 'firebase'. Baik sertakan atau hapus larik ketergantungan

Atau, jika saya mencoba dan menambahkan const ke callback, saya mendapatkan kesalahan yang mengatakan:

React Hook "useContext" tidak dapat dipanggil di dalam panggilan balik. React Hooks harus dipanggil dalam komponen fungsi Bereaksi atau fungsi React Hook kustom

PERIKSAAN BERIKUTNYA

Saya telah mengurangi file konfigurasi firebase saya menjadi hanya variabel konfigurasi (Saya akan menulis helpers di penyedia konteks untuk setiap konteks yang ingin saya gunakan).

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;  

Saya kemudian memiliki penyedia konteks auth sebagai berikut:

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

Saya juga membuat penyedia konteks firebase sebagai berikut:

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

Lalu, di index.js saya membungkus aplikasi di penyedia firebase

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

serviceWorker.unregister();

dan dalam daftar rute saya, saya telah membungkus rute yang relevan di penyedia auth:

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;

Pada upaya khusus ini, saya kembali ke masalah yang ditandai sebelumnya dengan kesalahan ini:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth bukan fungsi

Ini menunjuk ke baris penyedia auth ini sebagai penyebab masalah:

useEffect(() => {

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

Saya telah mencoba menggunakan huruf kapital F di Firebase dan menghasilkan kesalahan yang sama.

Ketika saya mencoba saran Tristan, saya menghapus semua hal itu dan mencoba dan mendefinisikan metode berhenti berlangganan saya sebagai metode yang tidak terdaftar (saya tidak tahu mengapa dia tidak menggunakan bahasa firebase - tetapi jika pendekatannya berhasil, saya akan berusaha lebih keras untuk mencari tahu mengapa). Ketika saya mencoba menggunakan solusinya, pesan kesalahan mengatakan:

TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ default (...) bukan fungsi

Jawaban untuk posting ini menyarankan untuk menghapus () dari setelah auth. Ketika saya mencoba itu, saya mendapatkan kesalahan yang mengatakan:

TypeError: Tidak dapat membaca properti 'onAuthStateChanged' dari undefined

Namun posting ini menyarankan masalah dengan cara firebase diimpor dalam file auth.

Saya telah mengimpor sebagai: impor Firebase dari "../firebase";

Firebase adalah nama kelas.

Video yang direkomendasikan Tristan adalah latar belakang yang membantu, tetapi saya saat ini ada di episode 9 dan masih belum menemukan bagian yang seharusnya membantu menyelesaikan masalah ini. Adakah yang tahu di mana menemukannya?

PERIKSA BERIKUTNYA Berikutnya - dan mencoba menyelesaikan masalah konteks saja - Saya telah mengimpor kedua createContext dan useContext dan mencoba menggunakannya seperti yang ditunjukkan dalam dokumentasi ini.

Saya tidak bisa menerima kesalahan yang mengatakan:

Kesalahan: Panggilan kait tidak valid. Kait hanya dapat disebut bagian dalam tubuh komponen fungsi. Ini bisa terjadi karena salah satu alasan berikut: ...

Saya telah melalui saran-saran dalam tautan ini untuk mencoba dan memecahkan masalah ini dan tidak dapat mengetahuinya. Saya tidak memiliki masalah yang ditunjukkan dalam panduan pemecahan masalah ini.

Saat ini - pernyataan konteks terlihat sebagai berikut:

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

Saya menghabiskan waktu menggunakan kursus ini untuk mencoba dan mencari tahu konteks dan elemen kait untuk masalah ini - setelah menontonnya - satu-satunya aspek untuk solusi yang diusulkan oleh Tristan di bawah ini adalah bahwa metode createContext tidak dipanggil dengan benar dalam posnya. itu harus "React.createContext" tetapi masih belum bisa menyelesaikan masalah.

Saya masih terjebak.

Adakah yang bisa melihat apa yang salah di sini?

Mel
sumber
Ini tidak terdefinisi karena Anda tidak mengimpornya.
Josh Pittman
3
Anda hanya perlu menambahkan exportke export const FirebaseContext?
Federkun
Hai Mel minta maaf atas jawaban yang lambat, saya sudah gila dua minggu dalam pekerjaan sehingga melihat komputer di malam hari adalah keluar dari pertanyaan! Saya telah memperbarui jawaban saya untuk Anda untuk memberikan solusi bersih dan sangat sederhana yang dapat Anda periksa.
Tristan Trainer
Terima kasih banyak - Saya akan melihatnya sekarang.
Mel
Hei Mel, baru saja diperbarui lebih lanjut dengan implementasi pembaruan Real Time yang benar dari firestore juga (dapat menghapus bagian onSnapshot agar tidak real time) jauh lebih pendek dan lebih ringkas sehingga orang lain yang melihatnya mungkin menemukan solusinya juga, terima kasih - maaf lagi untuk lambatnya respons
Tristan Trainer

Jawaban:

12

Sunting Utama : Butuh beberapa waktu untuk melihat lebih dalam tentang ini, inilah yang saya buat sebagai solusi yang lebih bersih, seseorang mungkin tidak setuju dengan saya tentang ini sebagai cara yang baik untuk mendekati ini.

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 }

Jika authUser adalah null maka tidak diautentikasi, jika pengguna memiliki nilai, maka diautentikasi.

firebaseConfigdapat ditemukan di Konsol firebase => Pengaturan Proyek => Aplikasi => Tombol Radio Config

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

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

Hook useEffect ini adalah inti untuk melacak perubahan otomatis pengguna. Kami menambahkan pendengar ke acara onAuthStateChanged dari firebase.auth () yang memperbarui nilai authUser. Metode ini mengembalikan panggilan balik untuk berhenti berlangganan pendengar ini yang bisa kita gunakan untuk membersihkan pendengar saat hook useFirebase di-refresh.

Ini adalah satu-satunya pengait yang kami butuhkan untuk otentikasi firebase (pengait lain dapat dibuat untuk firestore dll.

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("[email protected]", "password0");
  }

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

Ini adalah implementasi dasar dari Appkomponen 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 }
}

Ini dapat diimplementasikan dalam komponen Anda seperti:

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

}

Ini kemudian menghapus kebutuhan untuk Bereaksi useContext karena kami mendapatkan pembaruan langsung dari firebase itu sendiri.

Perhatikan beberapa hal:

  1. Menyimpan dokumen yang tidak diubah tidak memicu snapshot baru sehingga "oversaving" tidak menyebabkan rerender
  2. Saat memanggil getDocument, callback onUpdate langsung dipanggil dengan "snapshot" awal sehingga Anda tidak perlu kode tambahan untuk mendapatkan status awal dokumen.

Edit telah menghapus sebagian besar jawaban lama

Pelatih Tristan
sumber
1
Terima kasih untuk ini. Saya tidak bisa melihat bagaimana Anda mengatur ini. Dengan penyedia, saya mendapatkan kesalahan yang mengatakan createContext tidak didefinisikan. Itu karena sejauh ini belum ada konsumen. Di mana Anda meletakkan milik Anda?
Mel
Hai maaf createContext adalah bagian dari reaksi, jadi impor itu di atas sebagai {createContext} dari 'react' Saya menyadari saya lupa menunjukkan ke mana penyedia Firebase pergi, saya akan mengedit jawabannya
Tristan Trainer
Saya memang mengimpornya di penyedia, tetapi membuatnya tidak terdefinisi. Saya pikir itu karena tidak ada konsumen untuk itu
Mel
1
Konsumen adalah hook useContext (), tetapi melihat pertanyaan Anda lagi, sepertinya Anda tidak mengekspor FirebaseContext dari file - itulah sebabnya ia tidak dapat menemukan konteksnya :)
Tristan Trainer
1
Hai @Mel terima kasih itu sangat baik, saya harap itu terbukti bermanfaat pada akhirnya. Hooks dan Firebase keduanya cukup terlibat dan butuh waktu lama untuk menyelesaikannya dan saya mungkin belum menemukan solusi terbaik bahkan sekarang, saya mungkin membuat tutorial tentang pendekatan saya beberapa waktu karena lebih mudah untuk dijelaskan saat Anda membuat kode Itu.
Tristan Trainer
4

Firebase tidak terdefinisi karena Anda tidak mengimpornya. Pertama, harus firebase.firestore()seperti yang ditunjukkan pada contoh pada dokumen yang Anda tautkan ke https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore . Maka Anda harus benar-benar mengimpor firebase dalam file tersebut, sehingga import * as firebase from 'firebase';sebagaimana diuraikan dalam paket firebase readme https://www.npmjs.com/package/firebase

Josh Pittman
sumber
1
Saya mengimpornya di index.js
Mel
1
ReactDOM.render (<FirebaseContext.Provider value = {new Firebase ()}> <App /> </FirebaseContext.Provider>, document.getElementById ('root'));
Mel
1
Itu sebabnya pendekatan ini bekerja dengan componentDidMount
Mel
1
HOC withAuth juga dibungkus dengan Firebase.
Mel
3
Tetapi kesalahan Anda mengatakan itu tidak terdefinisi. Saya membantu Anda mendefinisikannya, dan Anda tidak memberi tahu saya apakah solusinya bekerja atau apa kesalahan yang dihasilkan. Anda selalu membuatnya sangat sulit untuk membantu Anda Mel. Maksud saya adalah Anda mengimpornya dalam file selain file komponen dashboard2, yang mana Anda referensi itu, yang menyebabkan kesalahan. Mengimport sesuatu dalam indeks tidak membantu bangunan Anda memahami apa itu dalam file yang sama sekali berbeda.
Josh Pittman
2

EDIT (3 Maret 2020):

Mari kita mulai dari awal.

  1. Saya telah membuat proyek baru:

    benang membuat aplikasi-isu firebase-hook-issue

  2. Saya telah menghapus semua 3 file App * yang dibuat secara default, menghapus impor dari index.js dan juga menghapus pekerja layanan untuk memiliki index.js yang bersih seperti itu:

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. Saya sudah memulai aplikasi untuk melihat bahwa Hello Firebase! dicetak.
  2. Saya telah menambahkan modul firebase
yarn add firebase
  1. Saya sudah menjalankan firebase init untuk mengatur firebase untuk proyek itu. Saya telah memilih salah satu proyek firebase kosong saya dan saya telah memilih Database dan Firestore, yang akhirnya membuat file-file berikutnya:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. Saya telah menambahkan impor untuk libs firebase dan juga menciptakan Firebase kelas dan FirebaseContext . Akhirnya saya membungkus komponen App di FirebaseContext.Provider dan mengatur nilainya ke instance Firebase () baru . Ini adalah kami hanya akan memiliki satu instance aplikasi Firebase yang dipakai sebagaimana mestinya karena itu pasti 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. Mari kita verifikasi bahwa kita dapat membaca apa pun dari Firestore. Untuk memverifikasi bacaan pertama, saya pergi ke proyek saya di Firebase Console, buka basis data Cloud Firestore saya dan menambahkan koleksi baru yang disebut penghitung dengan dokumen sederhana yang berisi satu bidang yang disebut nilai nomor jenis dan nilai 0. masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

  2. Kemudian saya telah memperbarui kelas App untuk menggunakan FirebaseContext yang telah kami buat, menggunakan hookStateState untuk hook counter sederhana kami dan menggunakan hook useEffect untuk membaca nilai dari firestore:

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

Catatan: Untuk menjaga jawaban sesingkat mungkin saya telah memastikan Anda tidak perlu diautentikasi dengan firebase, dengan mengatur akses ke firestore untuk berada dalam mode uji coba (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);
    }
  }
}

Jawaban saya sebelumnya: Anda dipersilakan untuk melihat kerangka reaksi-firebase-auth saya:

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

Ini terutama mengikuti artikel:

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

Namun agak ditulis ulang menggunakan kait. Saya telah menggunakannya setidaknya di dua proyek saya.

Penggunaan umum dari proyek peliharaan saya saat ini:

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

Dua bagian terpenting di sini adalah: - useAuthUser () hook yang menyediakan pengguna terotentikasi saat ini. - FirebaseContext yang saya gunakan melalui hook useContext .

const firebase = useContext(FirebaseContext);

Ketika Anda memiliki konteks untuk firebase, terserah Anda untuk mengimplementasikan objek firebase sesuai keinginan Anda. Terkadang saya memang menulis beberapa fungsi yang membantu, terkadang lebih mudah untuk hanya mengatur pendengar tepat di useEffect hook saya buat untuk komponen saya saat ini.

Salah satu bagian terbaik dari artikel itu adalah pembuatan dengan HOC Otorisasi , yang memungkinkan Anda menentukan prasyarat untuk mengakses halaman baik dalam komponen itu sendiri:

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

Atau mungkin bahkan mengatur kondisi tersebut tepat dalam implementasi router Anda.

Berharap bahwa dengan melihat repo dan artikel itu akan memberi Anda beberapa pemikiran ekstra baik untuk meningkatkan jawaban cemerlang lainnya untuk pertanyaan Anda.

fxdxpz
sumber
Saya membeli bukunya dan mengikuti pendekatannya. Saya menemukan pendekatan kondisi tidak benar-benar berfungsi ketika diimplementasikan dan protokol auth yang ditetapkan dalam buku itu gagal mempertahankan status melalui pembaruan komponen. Saya belum menemukan cara untuk menggunakan apa yang tercantum dalam buku itu. Bagaimanapun, terima kasih telah berbagi pemikiran Anda.
Mel
Saya tidak yakin apa yang Anda maksud. Sudahkah Anda mencoba aplikasi kerangka saya dengan proyek firebase Anda? Semua kondisi berfungsi sejauh yang saya tahu, karena saya telah menggunakannya di setidaknya 3 proyek.
fxdxpz