Odbiornik Firebase z hakami React

27

Próbuję wymyślić, jak korzystać z nasłuchiwania Firebase, aby dane firestore w chmurze były odświeżane za pomocą aktualizacji hooków reagowania.

Początkowo zrobiłem to przy użyciu komponentu klasy z funkcją componentDidMount, aby uzyskać dane 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,
  });  
}

To się psuje, gdy strona się aktualizuje, więc próbuję wymyślić, jak przesunąć słuchacza, aby zareagował na haki.

Zainstalowałem narzędzie hooks reagowania na ogień - chociaż nie mogę wymyślić, jak przeczytać instrukcje, aby móc go uruchomić.

Mam składnik funkcji w następujący sposób:

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

Ten komponent jest zawinięty w opakowanie authUser na poziomie trasy w następujący sposób:

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

Mam plik firebase.js, który podłącza się do firestore w następujący sposób:

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


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


  }

Definiuje także detektor, aby wiedzieć, kiedy zmienia się authUser:

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

Następnie w mojej konfiguracji kontekstowej bazy ogniowej mam:

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

Kontekst jest konfigurowany w opakowaniu z opcją FireFase w następujący sposób:

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;

Następnie w moim HOC z uwierzytelnianiem mam dostawcę kontekstu jako:

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;

Obecnie - kiedy próbuję tego, pojawia się błąd w komponencie Dashboard2, który mówi:

Baza ogniowa ”nie jest zdefiniowana

Próbowałem małych firebase i otrzymałem ten sam błąd.

Próbowałem także firebase.firestore i Firebase.firestore. Otrzymuję ten sam błąd.

Zastanawiam się, czy nie mogę używać mojego HOC ze składnikiem funkcji?

Widziałem tę aplikację demonstracyjną i ten post na blogu .

Postępując zgodnie z radami na blogu, stworzyłem nowy firebase / contextReader.jsx za pomocą:

 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
  }  

Następnie próbuję owinąć App.jsx w tym czytniku:

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

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

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth nie jest funkcją

Widziałem ten post dotyczący tego błędu i próbowałem odinstalować i ponownie zainstalować przędzę. To nie robi różnicy.

Kiedy patrzę na aplikację demonstracyjną , sugeruje to, że kontekst powinien zostać utworzony przy użyciu metody „interfejsu”. Nie widzę, skąd to pochodzi - nie mogę znaleźć odniesienia do wyjaśnienia tego w dokumentacji.

Nie mogę zrozumieć instrukcji poza próbowaniem tego, co zrobiłem, aby to podłączyć.

Widziałem ten post, który próbuje słuchać sklepu z ogniem bez użycia haczyków reagujących na ogień. Odpowiedzi wskazują na próbę wymyślenia, jak korzystać z tego narzędzia.

Przeczytałem to doskonałe wyjaśnienie, które opisuje, jak przejść od HOC do haków. Utknąłem z tym, jak zintegrować moduł nasłuchujący Firebase.

Widziałem ten post, który stanowi pomocny przykład, jak pomyśleć o zrobieniu tego. Nie jestem pewien, czy powinienem to zrobić w komponencie authListenerDidMount - czy w komponencie Dashboard, który próbuje go użyć.

NASTĘPNA PRÓBA Znalazłem ten post , który próbuje rozwiązać ten sam problem.

Kiedy próbuję wdrożyć rozwiązanie oferowane przez Shubham Khatri, konfiguruję konfigurację firebase w następujący sposób:

Dostawca kontekstu z: import React, {useContext} z „reaguj”; import Firebase z „../../firebase”;

const FirebaseContext = React.createContext(); 

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

Hak kontekstowy ma wtedy:

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;

Następnie w pliku index.js pakuję aplikację do dostawcy jako:

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

Następnie, gdy próbuję użyć detektora w komponencie, mam:

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;

I próbuję użyć go jako trasy bez składników lub opakowania uwierzytelniania:

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

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

Próba błędu importu: „FirebaseContext” nie jest eksportowany z „../Firebase/ContextHookProvider”.

Ten komunikat o błędzie ma sens, ponieważ ContextHookProvider nie eksportuje FirebaseContext - eksportuje FirebaseProvider - ale jeśli nie spróbuję zaimportować tego w Dashboard2 - nie będę mógł uzyskać do niego dostępu w funkcji, która próbuje go użyć.

Jednym z efektów ubocznych tej próby jest to, że moja metoda rejestracji już nie działa. Teraz generuje komunikat o błędzie:

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

Rozwiążę ten problem później - ale musi istnieć sposób, aby dowiedzieć się, jak używać reagowania z bazą ogniową, która nie wymaga miesięcy tej pętli przez miliony ścieżek, które nie działają, aby uzyskać podstawową konfigurację uwierzytelniania. Czy istnieje zestaw startowy do bazy ogniowej (firestore), który współpracuje z hakami reagującymi?

Podczas następnej próby próbowałem podążać za tym podejściem w tym udemy - ale działa to tylko w celu wygenerowania formularza - nie ma słuchacza, który mógłby ominąć trasy, aby dostosować się do uwierzytelnionego użytkownika.

Starałem się postępować zgodnie z tym podejściem w tym samouczku youtube - który ma to repozytorium do pracy. Pokazuje, jak używać haczyków, ale nie jak używać kontekstu.

NASTĘPNA PRÓBA Znalazłem to repozytorium, które wydaje się mieć dobrze przemyślane podejście do używania haków ze sklepem ogniowym. Nie mogę jednak zrozumieć kodu.

Sklonowałem to - i próbowałem dodać wszystkie pliki publiczne, a następnie po uruchomieniu - nie mogę tak naprawdę uruchomić kodu. Nie jestem pewien, czego brakuje w instrukcjach, jak to uruchomić, aby sprawdzić, czy w kodzie są lekcje, które mogą pomóc rozwiązać ten problem.

NASTĘPNA PRÓBA

Kupiłem szablon divjoy, który jest reklamowany jako konfigurowany dla firebase (nie jest konfigurowany dla firestore na wypadek, gdyby ktokolwiek rozważał to jako opcję).

Ten szablon proponuje opakowanie uwierzytelniające, które inicjuje konfigurację aplikacji - ale tylko dla metod uwierzytelniania - więc należy go zrestrukturyzować, aby umożliwić innemu dostawcy kontekstu dla magazynu ognia. Gdy przejdziesz przez ten proces i użyjesz procesu pokazanego w tym poście , pozostanie błąd w następującym wywołaniu zwrotnym:

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

Nie wie, co to jest baza ogniowa. Jest tak, ponieważ jest zdefiniowany w dostawcy kontekstu bazy ogniowej, który jest importowany i definiowany (w funkcji useProvideAuth ()) jako:

  const firebase = useContext(FirebaseContext)

Bez szans na oddzwonienie błąd mówi:

React use HookEffect ma brakującą zależność: „firebase”. Dołącz go lub usuń tablicę zależności

Lub jeśli spróbuję dodać tę const do wywołania zwrotnego, pojawia się błąd:

Nie można wywołać haka React „useContext” wewnątrz wywołania zwrotnego. Haczyki React muszą być wywoływane w komponencie funkcji React lub niestandardowej funkcji React Hook

NASTĘPNA PRÓBA

Zmniejszyłem mój plik konfiguracyjny bazy ogniowej do samych zmiennych konfiguracyjnych (napiszę pomocników u dostawców kontekstu dla każdego kontekstu, którego chcę użyć).

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;  

Następnie mam dostawcę kontekstu uwierzytelniania w następujący sposób:

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

Stworzyłem również kontekstowego dostawcę bazy ogniowej w następujący sposób:

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

Następnie w index.js zawijam aplikację do dostawcy firebase

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

serviceWorker.unregister();

i na mojej liście tras zawinąłem odpowiednie trasy w dostawcy uwierzytelniania:

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;

Po tej konkretnej próbie wracam do problemu oznaczonego wcześniej tym błędem:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth nie jest funkcją

Wskazuje ten wiersz dostawcy uwierzytelnienia jako stwarzający problem:

useEffect(() => {

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

Próbowałem użyć wielkiej litery F w Firebase i generuje ten sam błąd.

Kiedy wypróbowuję rady Tristana, usuwam wszystkie te rzeczy i próbuję zdefiniować moją metodę rezygnacji z subskrypcji jako metodę nie słuchania (nie wiem, dlaczego on nie używa języka bazy ogniowej - ale jeśli jego podejście zadziałałoby, postaram się bardziej dowiedzieć się, dlaczego). Gdy próbuję użyć jego rozwiązania, komunikat o błędzie mówi:

TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ domyślnie (...) nie jest funkcją

Odpowiedź na ten post sugeruje usunięcie () po auth. Gdy próbuję, pojawia się komunikat o błędzie:

TypeError: Nie można odczytać właściwości „onAuthStateChanged” niezdefiniowanej

Jednak ten post sugeruje problem ze sposobem importowania bazy ogniowej do pliku auth.

Mam go zaimportowany jako: import Firebase z „../firebase”;

Firebase to nazwa klasy.

Filmy zalecane przez Tristana są pomocne w tle, ale obecnie jestem w odcinku 9 i nadal nie znalazłem części, która powinna pomóc rozwiązać ten problem. Czy ktoś wie, gdzie to znaleźć?

NASTĘPNA PRÓBKA Następnie - i próbując rozwiązać tylko problem kontekstowy - zaimportowałem zarówno createContext, jak i useContext i próbowałem ich użyć, jak pokazano w tej dokumentacji.

Nie mogę dostać błędu, który mówi:

Błąd: nieprawidłowe połączenie hakowe. Haczyki można wywoływać tylko wewnątrz ciała komponentu funkcji. Może się to zdarzyć z jednego z następujących powodów: ...

Przeszedłem przez sugestie zawarte w tym linku, aby spróbować rozwiązać ten problem i nie mogę go rozwiązać. Nie mam żadnych problemów pokazanych w tym przewodniku rozwiązywania problemów.

Obecnie - instrukcja kontekstu wygląda następująco:

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

Spędziłem czas, wykorzystując ten kurs udemy, aby dowiedzieć się, jaki jest kontekst i element tego problemu - po jego obejrzeniu - jedynym aspektem rozwiązania zaproponowanego przez Tristana poniżej jest to, że metoda createContext nie została poprawnie wywołana w jego poście. musi to być „React.createContext”, ale nadal nie jest blisko rozwiązania problemu.

Nadal utknąłem.

Czy ktoś może zobaczyć, co poszło nie tak?

Mel
źródło
Jest niezdefiniowany, ponieważ go nie importujesz.
Josh Pittman
3
nie wystarczy dodać exportdo export const FirebaseContext?
Federkun
Cześć Mel przepraszam za powolną odpowiedź, miałem szalone dwa tygodnie w pracy, więc wieczorem nie mogłem patrzeć na komputer! Zaktualizowałem moją odpowiedź, aby zapewnić czyste i bardzo proste rozwiązanie, które można sprawdzić.
Trener Tristana
Wielkie dzięki - teraz na to spojrzę.
Mel
Hej Mel, właśnie zaktualizowałem z poprawną implementacją aktualizacji w czasie rzeczywistym ze firestore (można usunąć część onSnapshot, aby nie była w czasie rzeczywistym) Jeśli to jest rozwiązanie, proszę zasugerować ewentualne zaktualizowanie pytania, aby stało się dużo krótszy i bardziej zwięzły, więc inni, którzy go oglądają, również mogą znaleźć rozwiązanie, dziękuję - jeszcze raz przepraszam za powolny charakter odpowiedzi
Trener Tristana

Odpowiedzi:

12

Ważna edycja : Zajęło mi trochę czasu, aby przyjrzeć się temu nieco bardziej. Oto, co wymyśliłem, jest czystszym rozwiązaniem, ktoś może się ze mną nie zgodzić, że jest to dobry sposób, aby podejść do tego.

Użyj zaczepu uwierzytelniania 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 }

Jeśli authUser ma wartość NULL, nie jest uwierzytelniany, jeśli użytkownik ma wartość, jest uwierzytelniany.

firebaseConfigmożna znaleźć na konsoli Firebase => Ustawienia projektu => Aplikacje => Konfiguruj przycisk radiowy

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

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

Ten haczyk useEffect jest podstawą do śledzenia zmian autoryzacji użytkownika. Dodajemy detektor do zdarzenia onAuthStateChanged firebase.auth (), które aktualizuje wartość authUser. Ta metoda zwraca wywołanie zwrotne do wypisania się z detektora, którego możemy użyć do wyczyszczenia detektora po odświeżeniu haka useFirebase.

Jest to jedyny hak, którego potrzebujemy do uwierzytelnienia bazy ogniowej (inne haki mogą być wykonane dla magazynu itp.

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

Jest to podstawowa implementacja Appkomponentucreate-react-app

Hak bazy danych 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 }
}

Można to zaimplementować w swoim komponencie w następujący sposób:

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

}

To eliminuje potrzebę użycia React useContext, ponieważ otrzymujemy aktualizacje bezpośrednio z samej bazy ogniowej.

Zwróć uwagę na kilka rzeczy:

  1. Zapisanie niezmienionego dokumentu nie wyzwala nowej migawki, więc „oversaving” nie powoduje ponownego wysyłania
  2. Po wywołaniu getDocument wywołanie zwrotne onUpdate jest wywoływane od razu z początkowym „snapshotem”, więc nie potrzebujesz dodatkowego kodu, aby uzyskać początkowy stan dokumentu.

Edycja usunęła dużą część starej odpowiedzi

Trener Tristana
źródło
1
dzięki za to. Nie widzę, jak to skonfigurowałeś. W przypadku dostawcy pojawia się błąd informujący, że createContext nie jest zdefiniowany. To dlatego, że do tej pory nie ma konsumenta. Gdzie położyłeś swój?
Mel
Hej, przepraszam, createContext jest częścią React, więc zaimportuj go u góry, ponieważ {createContext} z „Reaguj” Zdałem sobie sprawę, że zapomniałem pokazać, dokąd idzie dostawca Firebase, edytuję odpowiedź
Tristan Trainer
Zaimportowałem go do dostawcy, ale renderowanie jest niezdefiniowane. Myślę, że dzieje się tak, ponieważ nie ma na to konsumenta
Mel
1
Konsument jest hakiem useContext (), ale patrząc ponownie na twoje pytanie, wygląda na to, że nie eksportujesz FirebaseContext z pliku - dlatego nie może znaleźć kontekstu :)
Tristan Trainer
1
Cześć, dziękuję, to bardzo miłe, mam nadzieję, że okaże się pomocne w końcu. Zarówno Haki, jak i Firebase są dość zaangażowane i zajęło mi dużo czasu, aby obejść to dookoła i być może nie znalazłem najlepszego rozwiązania, nawet teraz mogę stworzyć samouczek na temat mojego podejścia, ponieważ łatwiej jest to wyjaśnić podczas pisania kodu to.
Trener Tristana
4

Baza ogniowa jest niezdefiniowana, ponieważ jej nie importujesz. Po pierwsze, musi być firebase.firestore()tak, jak pokazano w przykładzie na dokumentach, które łączysz z https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore . Następnie musisz zaimportować firebase do pliku, tak import * as firebase from 'firebase';jak opisano w pakiecie firebase readme https://www.npmjs.com/package/firebase

Josh Pittman
źródło
1
Importuję go do index.js
Mel
1
ReactDOM.render (<FirebaseContext.Provider value = {new Firebase ()}> <App /> </FirebaseContext.Provider>, document.getElementById ('root'));
Mel
1
Właśnie dlatego to podejście działa z componentDidMount
Mel
1
WithAuth HOC jest również zawarty w Firebase.
Mel
3
Ale twój błąd mówi, że jest niezdefiniowany. Pomagam ci to zdefiniować i nie mówisz mi, czy rozwiązanie działa, ani jaki jest wynikowy błąd. Zawsze bardzo trudno jest ci pomóc Mel. Chodzi mi o to, że importujesz go do pliku innego niż plik komponentu dashboard2, do którego się odwołujesz, co powoduje błąd. Zaimportowanie czegoś do indeksu nie pomaga twojej kompilacji zrozumieć, co znajduje się w zupełnie innym pliku.
Josh Pittman
2

EDYCJA (3 marca 2020):

Zacznijmy od zera.

  1. Stworzyłem nowy projekt:

    przędza tworzy problem z hakiem bazy reagującej aplikacji

  2. Usunąłem wszystkie 3 pliki aplikacji * utworzone domyślnie, usunąłem import z index.js, a także usunąłem pracownika serwisu, aby mieć czyste index.js w ten sposób:

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. Uruchomiłem aplikację, by zobaczyć Hello Firebase! jest wydrukowany.
  2. Dodałem moduł firebase
yarn add firebase
  1. Uruchomiłem init Firebase, aby skonfigurować FireBase dla tego projektu. Wybrałem jeden z moich pustych projektów firebase i wybrałem Database i Firestore, które kończą się tworzeniem kolejnych plików:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. Dodałem importu dla Firebase bibliotekami, a także stworzył Firebase klasę i FirebaseContext . W końcu otoczyłem aplikację komponentem FirebaseContext.Provider i ustawiłem jej wartość na nową instancję Firebase () . Będziemy mieli tylko jedną instancję aplikacji Firebase utworzoną tak, jak powinniśmy, ponieważ musi to być 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. Sprawdźmy, czy możemy przeczytać wszystko z Firestore. Aby zweryfikować tylko czytanie, poszedłem do mojego projektu w Firebase Console, otworzyłem bazę danych Cloud Firestore i dodałem nową kolekcję o nazwie liczniki z prostym dokumentem zawierającym jedno pole o nazwie wartość typu numer i wartość 0. wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

  2. Następnie zaktualizowałem klasę aplikacji, aby korzystała z FirebaseContext, który stworzyliśmy, wykonałem hook useState dla naszego prostego counter hooka i użyłem hook useEffect do odczytu wartości ze 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"));

Uwaga: Aby odpowiedź była jak najkrótsza, upewniłem się, że nie musisz być uwierzytelniany za pomocą firebase, ustawiając dostęp do firestore w trybie testowym (plik 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);
    }
  }
}

Moja poprzednia odpowiedź: Zapraszamy do zapoznania się z moim szkieletem reagującym na ogień:

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

Wynika to głównie z artykułu:

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

Ale nieco przepisany na używanie haków. Użyłem go w co najmniej dwóch moich projektach.

Typowe zastosowanie z mojego obecnego projektu dla zwierząt domowych:

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

Dwie najważniejsze części: - hook useAuthUser (), który zapewnia bieżącemu uwierzytelnionemu użytkownikowi. - FirebaseContext, którego używam poprzez hook useContext .

const firebase = useContext(FirebaseContext);

Gdy masz kontekst do bazy ogniowej, to od Ciebie zależy, czy zaimplementujesz obiekt bazy ogniowej według własnych upodobań. Czasami piszę kilka pomocnych funkcji, czasem łatwiej jest po prostu skonfigurować detektory bezpośrednio w haku useEffect, który tworzę dla mojego bieżącego komponentu.

Jedną z najlepszych części tego artykułu było utworzenie withAuthorization HOC, który pozwala określić warunki dostępu do strony w samym komponencie:

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

A może nawet ustawia te warunki bezpośrednio w implementacji routera.

Mam nadzieję, że zapoznanie się z repozytorium i artykułem dostarczy ci dodatkowych dobrych przemyśleń, aby uwydatnić inne genialne odpowiedzi na twoje pytanie.

fxdxpz
źródło
Kupiłem jego książkę i podążyłem za nim. Odkryłem, że podejście oparte na warunkach faktycznie nie działało po wdrożeniu i że protokół autoryzacji określony w tej książce nie zachował statusu poprzez aktualizacje składników. Nie znalazłem sposobu na wykorzystanie tego, co jest zawarte w tej książce. W każdym razie dziękuję za podzielenie się swoimi przemyśleniami.
Mel
Nie jestem pewien co masz na myśli. Czy wypróbowałeś moją aplikację szkieletową w swoim projekcie Firebase? O ile wiem, wszystkie warunki działają, ponieważ używałem go w co najmniej 3 projektach.
fxdxpz