Écouteur Firebase avec React Hooks

27

J'essaie de comprendre comment utiliser un écouteur Firebase afin que les données du cloud firestore soient actualisées avec les mises à jour de hooks de réaction.

Initialement, j'ai fait cela en utilisant un composant de classe avec une fonction componentDidMount pour obtenir les données du 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,
  });  
}

Cela se casse lorsque la page est mise à jour, j'essaie donc de comprendre comment déplacer l'auditeur pour réagir aux hooks.

J'ai installé l' outil react-firebase-hooks - bien que je n'arrive pas à comprendre comment lire les instructions pour pouvoir le faire fonctionner.

J'ai un composant de fonction comme suit:

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

Ce composant est encapsulé dans un wrapper authUser au niveau de l'itinéraire comme suit:

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

J'ai un fichier firebase.js, qui se branche sur firestore comme suit:

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


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


  }

Il définit également un écouteur pour savoir quand l'authUser change:

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

Ensuite, dans ma configuration de contexte Firebase, j'ai:

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

Le contexte est configuré dans un wrapper withFirebase comme suit:

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;

Ensuite, dans mon withAuthentication HOC, j'ai un fournisseur de contexte comme:

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;

Actuellement - quand j'essaye ceci, j'obtiens une erreur dans le composant Dashboard2 qui dit:

Firebase 'n'est pas défini

J'ai essayé la base de feu en minuscules et j'obtiens la même erreur.

J'ai également essayé firebase.firestore et Firebase.firestore. J'ai la même erreur.

Je me demande si je ne peux pas utiliser mon HOC avec un composant de fonction?

J'ai vu cette application de démonstration et cet article de blog .

En suivant les conseils du blog, j'ai créé un nouveau firebase / contextReader.jsx avec:

 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
  }  

Ensuite, j'essaie d'envelopper mon App.jsx dans ce lecteur avec:

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

Lorsque j'essaye ceci, j'obtiens une erreur qui dit:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth n'est pas une fonction

J'ai vu ce post traiter de cette erreur et j'ai essayé de désinstaller et réinstaller le fil. Ça ne fait aucune différence.

Lorsque je regarde l' application de démonstration , cela suggère que le contexte devrait être créé à l'aide d'une méthode «d'interface». Je ne vois pas d'où cela vient - je ne trouve pas de référence pour l'expliquer dans la documentation.

Je ne peux pas comprendre les instructions à part essayer ce que j'ai fait pour le brancher.

J'ai vu ce post qui tente d'écouter Firestore sans utiliser de hooks de base de réaction. Les réponses renvoient à essayer de comprendre comment utiliser cet outil.

J'ai lu cette excellente explication qui explique comment passer des HOC aux crochets. Je suis coincé avec la façon d'intégrer l'écouteur Firebase.

J'ai vu cet article qui fournit un exemple utile pour savoir comment procéder. Je ne sais pas si je devrais essayer de le faire dans le composant authListenerDidMount - ou dans le composant Dashboard qui essaie de l'utiliser.

PROCHAINE TENTATIVE J'ai trouvé ce message , qui tente de résoudre le même problème.

Lorsque j'essaie d'implémenter la solution proposée par Shubham Khatri, j'ai configuré la configuration Firebase comme suit:

Un fournisseur de contexte avec: import React, {useContext} de 'react'; importer Firebase depuis '../../firebase';

const FirebaseContext = React.createContext(); 

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

Le hook de contexte a alors:

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;

Ensuite, dans l'index.js, j'encapsule l'application dans le fournisseur en tant que:

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

Ensuite, lorsque j'essaie d'utiliser l'écouteur dans le composant, j'ai:

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;

Et j'essaie de l'utiliser comme une route sans composants ni wrapper d'authentification:

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

Lorsque j'essaye ceci, j'obtiens une erreur qui dit:

Tentative d’importation: «FirebaseContext» n’est pas exporté depuis «../Firebase/ContextHookProvider».

Ce message d'erreur est logique, car ContextHookProvider n'exporte pas FirebaseContext - il exporte FirebaseProvider - mais si je n'essaie pas d'importer cela dans Dashboard2 - alors je ne peux pas y accéder dans la fonction qui essaie de l'utiliser.

Un effet secondaire de cette tentative est que ma méthode d'inscription ne fonctionne plus. Il génère maintenant un message d'erreur qui dit:

TypeError: Impossible de lire la propriété «doCreateUserWithEmailAndPassword» de null

Je résoudrai ce problème plus tard, mais il doit y avoir un moyen de comprendre comment utiliser React avec FireBase qui n'implique pas des mois de cette boucle à travers des millions de voies qui ne fonctionnent pas pour obtenir une configuration d'authentification de base. Existe-t-il un kit de démarrage pour Firebase (Firestore) qui fonctionne avec les crochets React?

Prochaine tentative, j'ai essayé de suivre l'approche de ce cours udemy - mais cela ne fonctionne que pour générer une entrée de formulaire - il n'y a pas d'écouteur pour contourner les routes à ajuster avec l'utilisateur authentifié.

J'ai essayé de suivre l'approche de ce tutoriel YouTube - sur lequel ce repo peut fonctionner. Il montre comment utiliser les hooks, mais pas comment utiliser le contexte.

PROCHAINE TENTATIVE J'ai trouvé ce repo qui semble avoir une approche bien pensée pour utiliser des crochets avec un coupe-feu. Cependant, je ne peux pas comprendre le code.

J'ai cloné cela - et j'ai essayé d'ajouter tous les fichiers publics, puis quand je l'ai exécuté - je ne peux pas réellement faire fonctionner le code. Je ne suis pas sûr de ce qui manque dans les instructions pour savoir comment exécuter cela afin de voir s'il y a des leçons dans le code qui peuvent aider à résoudre ce problème.

PROCHAINE TENTATIVE

J'ai acheté le modèle divjoy, qui est annoncé comme étant configuré pour Firebase (il n'est pas configuré pour Firestore au cas où quelqu'un d'autre envisage cela comme une option).

Ce modèle propose un wrapper d'authentification qui initialise la configuration de l'application - mais uniquement pour les méthodes d'authentification - il doit donc être restructuré pour autoriser un autre fournisseur de contexte pour Firestore. Lorsque vous vous trompez à travers ce processus et utilisez le processus montré dans ce post , ce qui reste est une erreur dans le rappel suivant:

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

Il ne sait pas ce qu'est la base de feu. En effet, il est défini dans le fournisseur de contexte Firebase qui est importé et défini (dans la fonction useProvideAuth ()) comme:

  const firebase = useContext(FirebaseContext)

Sans chances de rappel, l'erreur indique:

React Hook useEffect a une dépendance manquante: 'firebase'. Soit l'inclure ou supprimer le tableau de dépendances

Ou, si j'essaye d'ajouter cette constante au rappel, j'obtiens une erreur qui dit:

React Hook "useContext" ne peut pas être appelé dans un rappel. React Hooks doit être appelé dans un composant de fonction React ou une fonction React Hook personnalisée

PROCHAINE TENTATIVE

J'ai réduit mon fichier de configuration Firebase à seulement des variables de configuration (j'écrirai des assistants dans les fournisseurs de contexte pour chaque contexte que je veux utiliser).

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;  

J'ai ensuite un fournisseur de contexte d'authentification comme suit:

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

J'ai également créé un fournisseur de contexte Firebase comme suit:

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

Ensuite, dans index.js, j'encapsule l'application dans le fournisseur firebase

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

serviceWorker.unregister();

et dans ma liste de routes, j'ai encapsulé les routes pertinentes dans le fournisseur d'authentification:

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;

Sur cette tentative particulière, je reviens au problème signalé plus tôt avec cette erreur:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth n'est pas une fonction

Il indique que cette ligne du fournisseur d'authentification crée le problème:

useEffect(() => {

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

J'ai essayé d'utiliser F majuscule dans Firebase et cela génère la même erreur.

Lorsque j'essaye le conseil de Tristan, je supprime toutes ces choses et essaie de définir ma méthode de désabonnement comme une méthode non écoutée (je ne sais pas pourquoi il n'utilise pas le langage firebase - mais si son approche fonctionnait, j'essaierais plus fort pour comprendre pourquoi). Lorsque j'essaie d'utiliser sa solution, le message d'erreur indique:

TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ par défaut (...) n'est pas une fonction

La réponse à ce post suggère de supprimer () après l'authentification. Lorsque j'essaye, j'obtiens une erreur qui dit:

TypeError: Impossible de lire la propriété «onAuthStateChanged» de non défini

Cependant, ce message suggère un problème avec la façon dont Firebase est importé dans le fichier d'authentification.

Je l'ai importé en tant que: importez Firebase de "../firebase";

Firebase est le nom de la classe.

Les vidéos recommandées par Tristan sont utiles, mais je suis actuellement dans l'épisode 9 et je ne trouve toujours pas la partie censée aider à résoudre ce problème. Est-ce que quelqu'un sait où trouver ça?

PROCHAINE TENTATIVE Ensuite - et en essayant de résoudre le problème de contexte uniquement - j'ai importé à la fois createContext et useContext et j'ai essayé de les utiliser comme indiqué dans cette documentation.

Je ne peux pas passer une erreur qui dit:

Erreur: appel de raccordement non valide. Les crochets ne peuvent être appelés qu'à l'intérieur du corps d'un composant de fonction. Cela peut se produire pour l'une des raisons suivantes: ...

J'ai parcouru les suggestions de ce lien pour essayer de résoudre ce problème et je ne peux pas le comprendre. Je n'ai aucun des problèmes indiqués dans ce guide de dépannage.

Actuellement, l'énoncé de contexte se présente comme suit:

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

J'ai passé du temps à utiliser ce cours udemy pour essayer de comprendre le contexte et les éléments liés à ce problème - après l'avoir vu - le seul aspect de la solution proposée par Tristan ci-dessous est que la méthode createContext n'est pas appelée correctement dans son article. il doit s'agir de "React.createContext" mais il ne parvient toujours pas à résoudre le problème.

Je suis toujours coincé.

Quelqu'un peut-il voir ce qui ne va pas ici?

Mel
la source
Il n'est pas défini car vous ne l'importez pas.
Josh Pittman
3
avez-vous juste besoin d'ajouter exportà export const FirebaseContext?
Federkun
Salut Mel, excuse-toi pour la lenteur de la réponse, j'ai eu deux semaines de travail fou, donc regarder un ordinateur le soir était hors de question! J'ai mis à jour ma réponse pour vous fournir une solution propre et très simple que vous pouvez vérifier.
Tristan Trainer
Merci beaucoup - je vais l'examiner maintenant.
Mel
Hey Mel, je viens de mettre à jour la mise en œuvre correcte des mises à jour en temps réel de Firestore (peut supprimer la partie onSnapshot pour qu'elle ne soit pas en temps réel) Si c'est la solution, je peux suggérer de mettre à jour votre question pour en faire un beaucoup plus court et plus concis pour que les autres utilisateurs le trouvant puissent également trouver la solution, merci - désolé encore pour la lenteur des réponses
Tristan Trainer

Réponses:

12

Major Edit : Il a fallu un certain temps pour approfondir les choses. Voici ce que j'ai trouvé est une solution plus propre, quelqu'un pourrait être en désaccord avec moi sur le fait que c'est une bonne façon d'aborder cela.

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 }

Si authUser est null, il n'est pas authentifié, si l'utilisateur a une valeur, puis authentifié.

firebaseConfigpeut être trouvé sur la console Firebase => Paramètres du projet => Applications => Bouton radio de configuration

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

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

Ce hook useEffect est le cœur du suivi des changements d'authentification d'un utilisateur. Nous ajoutons un écouteur à l'événement onAuthStateChanged de firebase.auth () qui met à jour la valeur de authUser. Cette méthode renvoie un rappel pour désinscrire cet écouteur que nous pouvons utiliser pour nettoyer l'écouteur lorsque le hook useFirebase est actualisé.

C'est le seul crochet dont nous avons besoin pour l'authentification Firebase (d'autres crochets peuvent être fabriqués pour le magasin de pompiers, etc.

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

Il s'agit d'une implémentation de base du Appcomposant d'uncreate-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 }
}

Cela peut être implémenté dans votre composant comme suit:

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

}

Cela supprime alors le besoin de React useContext car nous obtenons des mises à jour directement à partir de Firebase lui-même.

Remarquez deux ou trois choses:

  1. L'enregistrement d'un document inchangé ne déclenche pas un nouvel instantané, donc la «sur-sauvegarde» ne provoque pas de redondances
  2. Lors de l'appel à getDocument, le rappel onUpdate est appelé immédiatement avec un "instantané" initial, vous n'avez donc pas besoin de code supplémentaire pour obtenir l'état initial du document.

Modifier a supprimé une grande partie de l'ancienne réponse

Tristan Trainer
la source
1
Merci pour cela. Je ne vois pas comment vous avez mis cela en place. Avec le fournisseur, j'obtiens une erreur indiquant que createContext n'est pas défini. C'est parce qu'il n'y a pas de consommateur jusqu'à présent. Où avez-vous mis le vôtre?
Mel
Hé désolé, createContext fait partie de react, alors importez-le en haut en tant que {createContext} de 'react'. J'ai réalisé que j'avais oublié de montrer où va le fournisseur Firebase, je vais modifier la réponse
Tristan Trainer
Je l'ai importé dans le fournisseur, mais il n'est pas défini. Je pense que c'est parce qu'il n'y a pas de consommateur pour ça
Mel
1
Le consommateur est le crochet useContext (), mais en regardant à nouveau votre question, il semble que vous n'exportez pas FirebaseContext à partir du fichier - c'est pourquoi il ne peut pas trouver le contexte :)
Tristan Trainer
1
Bonjour @Mel merci c'est très gentil, j'espère que ça se révèle utile au final. Les crochets et Firebase sont tous les deux très impliqués et il m'a fallu beaucoup de temps pour m'en occuper et je n'ai peut-être pas encore trouvé la meilleure solution, je pourrais peut-être créer un tutoriel sur mon approche quelque temps car il est plus facile de l'expliquer lorsque vous codez il.
Tristan Trainer
4

Firebase n'est pas défini car vous ne l'importez pas. Tout d'abord, son besoin doit être firebase.firestore()comme indiqué dans l'exemple sur les documents que vous avez liés à https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore . Ensuite, vous devez réellement importer Firebase dans le fichier, import * as firebase from 'firebase';comme indiqué dans le fichier Lisez-moi du package Firebase https://www.npmjs.com/package/firebase

Josh Pittman
la source
1
Je l'importe dans index.js
Mel
1
ReactDOM.render (<FirebaseContext.Provider value = {new Firebase ()}> <App /> </FirebaseContext.Provider>, document.getElementById ('root'));
Mel
1
C'est pourquoi l'approche fonctionne avec componentDidMount
Mel
1
Le withAuth HOC est également intégré à withFirebase.
Mel
3
Mais votre erreur indique qu'elle n'est pas définie. Je vous aide à la définir et vous ne me dites pas si la solution fonctionne ou quelle est l'erreur résultante. Il est toujours très difficile de vous aider Mel. Mon point est que vous l'importez dans un fichier autre que le fichier de composant dashboard2, qui est l'endroit où vous le référencez, ce qui provoque l'erreur. Imorter quelque chose dans l'index n'aide pas votre build à comprendre ce qu'il contient dans un fichier complètement différent.
Josh Pittman
2

EDIT (3 mars 2020):

Commençons à zéro.

  1. J'ai créé un nouveau projet:

    fil crée un problème de crochet de base de réaction

  2. J'ai supprimé les 3 fichiers App * créés par défaut, supprimé l'importation d'Index.js et supprimé également le service worker pour avoir un index.js propre comme celui-ci:

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. J'ai démarré l'application pour voir Hello Firebase! est imprimé.
  2. J'ai ajouté un module Firebase
yarn add firebase
  1. J'ai exécuté Firebase init pour configurer Firebase pour ce projet. J'ai choisi l'un de mes projets Firebase vides et j'ai sélectionné Database et Firestore, qui finissent par créer les fichiers suivants:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. J'ai ajouté des importations pour les bibliothèques Firebase et j'ai également créé une Firebase et classe Firebase et FirebaseContext . Enfin, j'ai encapsulé l'application dans le composant FirebaseContext.Provider et défini sa valeur sur une nouvelle instance Firebase () . Nous allions avoir une seule instance de l'application Firebase instanciée comme nous le devrions, car il doit s'agir d'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. Vérifions que nous pouvons lire n'importe quoi dans Firestore. Pour vérifier juste la lecture d'abord, je suis allé à mon projet dans Firebase Console, ouvrir ma base de données Cloud Firestore et ajouté une nouvelle collection appelée compteurs avec un document simple contenant un champ appelé valeur de type numéro et valeur 0. entrez la description de l'image ici entrez la description de l'image ici

  2. Ensuite, j'ai mis à jour la classe App pour utiliser FirebaseContext que nous avons créé, fait le crochet useState pour notre simple crochet de compteur et utilisé le crochet useEffect pour lire la valeur du magasin de pompiers:

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

Remarque: Pour garder la réponse aussi courte que possible, je me suis assuré que vous n'avez pas besoin d'être authentifié avec Firebase, en définissant l'accès au firestore pour qu'il soit en mode test (fichier 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);
    }
  }
}

Ma réponse précédente: Vous êtes plus que bienvenus pour jeter un œil à mon squelette react-firebase-auth:

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

Il suit principalement l'article:

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

Mais quelque peu réécrit pour utiliser des crochets. Je l'ai utilisé dans au moins deux de mes projets.

Utilisation typique de mon projet actuel pour animaux de compagnie:

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

Les deux parties les plus importantes ici sont: - le crochet useAuthUser () qui fournit l'utilisateur authentifié actuel. - FirebaseContext que j'utilise via le hook useContext .

const firebase = useContext(FirebaseContext);

Lorsque vous avez un contexte pour firebase, c'est à vous d'implémenter un objet firebase à votre convenance. Parfois, j'écris des fonctions utiles, parfois il est plus facile de configurer les écouteurs directement dans useEffect crochet je crée pour mon composant actuel.

L'une des meilleures parties de cet article a été la création de withAuthorization HOC, qui vous permet de spécifier les conditions préalables pour accéder à la page dans le composant lui-même:

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

Ou peut-être même définir ces conditions directement dans la mise en œuvre de votre routeur.

J'espère que la lecture du dépôt et de l'article vous donnera de bonnes pensées supplémentaires pour améliorer d'autres réponses brillantes à votre question.

fxdxpz
la source
J'ai acheté son livre et j'ai suivi son approche. J'ai trouvé que l'approche des conditions ne fonctionnait pas réellement lorsqu'elle était implémentée et que le protocole d'authentification décrit dans ce livre n'était pas en mesure de maintenir le statut via les mises à jour des composants. Je n'ai pas trouvé de moyen d'utiliser ce qui est décrit dans ce livre. Merci quand même de partager vos pensées.
Mel
Je ne suis pas sûr de ce que tu veux dire. Avez-vous essayé mon application squelette avec votre projet Firebase? Toutes les conditions fonctionnent pour autant que je sache, car je l'ai utilisé dans au moins 3 projets.
fxdxpz