Firebase oyente con React Hooks

27

Estoy tratando de descubrir cómo usar un escucha de Firebase para que los datos del almacén de incendios en la nube se actualicen con actualizaciones de ganchos de reacción.

Inicialmente, hice esto usando un componente de clase con una función componentDidMount para obtener los datos del almacén de incendios.

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

Eso se rompe cuando la página se actualiza, así que estoy tratando de descubrir cómo mover al oyente para que reaccione.

He instalado la herramienta react-firebase-hooks , aunque no puedo entender cómo leer las instrucciones para que funcione.

Tengo un componente de función de la siguiente manera:

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

Este componente está envuelto en un contenedor authUser en el nivel de ruta de la siguiente manera:

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

Tengo un archivo firebase.js, que se conecta a firestore de la siguiente manera:

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


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


  }

También define un oyente para saber cuándo cambia el 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;
        }
    });
  };

Luego, en mi configuración de contexto de Firebase tengo:

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

El contexto se configura en un contenedor withFirebase de la siguiente manera:

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;

Luego, en mi withAuthentication HOC, tengo un proveedor de contexto como:

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;

Actualmente, cuando intento esto, aparece un error en el componente Dashboard2 que dice:

Firebase 'no está definido

Intenté Firebase en minúsculas y obtuve el mismo error.

También probé firebase.firestore y Firebase.firestore. Me sale el mismo error.

Me pregunto si no puedo usar mi HOC con un componente de función.

He visto esta aplicación de demostración y esta publicación de blog .

Siguiendo los consejos del blog, hice un nuevo firebase / contextReader.jsx con:

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



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

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

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

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

    return state
  }  

Luego trato de envolver mi App.jsx en ese lector con:

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

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


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

Cuando intento esto, aparece un error que dice:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth no es una función

He visto esta publicación que trata sobre ese error y he intentado desinstalar y reinstalar hilo. No hace ninguna diferencia.

Cuando miro la aplicación de demostración , sugiere que el contexto debe crearse utilizando un método de 'interfaz'. No puedo ver de dónde viene esto: no puedo encontrar una referencia para explicarlo en la documentación.

No puedo entender las instrucciones más que intentar lo que he hecho para conectar esto.

He visto esta publicación que intenta escuchar firestore sin usar react-firebase-hooks. Las respuestas apuntan a tratar de descubrir cómo usar esta herramienta.

He leído esta excelente explicación que cómo pasar de los HOC a los ganchos. Estoy atrapado en cómo integrar el oyente firebase.

He visto esta publicación que proporciona un ejemplo útil de cómo pensar en hacer esto. No estoy seguro de si debería intentar hacer esto en el componente authListenerDidMount, o en el componente Panel que está tratando de usarlo.

PRÓXIMO INTENTO Encontré esta publicación , que está tratando de resolver el mismo problema.

Cuando trato de implementar la solución ofrecida por Shubham Khatri, configuro la configuración de Firebase de la siguiente manera:

Un proveedor de contexto con: import React, {useContext} de 'react'; importar Firebase desde '../../firebase';

const FirebaseContext = React.createContext(); 

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

El gancho de contexto tiene entonces:

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;

Luego, en index.js, envuelvo la aplicación en el proveedor como:

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

Luego, cuando trato de usar el oyente en el componente que tengo:

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;

Y trato de usarlo como una ruta sin componentes o envoltorio de autenticación:

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

Cuando intento esto, aparece un error que dice:

Intento de error de importación: 'FirebaseContext' no se exporta desde '../Firebase/ContextHookProvider'.

Ese mensaje de error tiene sentido, porque ContextHookProvider no exporta FirebaseContext, exporta FirebaseProvider, pero si no intento importar esto en Dashboard2, entonces no puedo acceder a él en la función que intenta usarlo.

Un efecto secundario de este intento es que mi método de registro ya no funciona. Ahora genera un mensaje de error que dice:

TypeError: no se puede leer la propiedad 'doCreateUserWithEmailAndPassword' de null

Resolveré este problema más tarde, pero debe haber una manera de descubrir cómo usar Reaccionar con Firebase que no implique meses de este ciclo a través de millones de vías que no funcionan para obtener una configuración de autenticación básica. ¿Existe un kit de inicio para firebase (firestore) que funcione con ganchos de reacción?

En el siguiente intento , intenté seguir el enfoque en este curso udemy , pero solo funciona para generar una entrada de formulario, no hay un oyente para colocar las rutas para ajustar con el usuario autenticado.

Traté de seguir el enfoque en este tutorial de YouTube , que tiene este repositorio para trabajar. Muestra cómo usar ganchos, pero no cómo usar el contexto.

PRÓXIMO INTENTO Encontré este repositorio que parece tener un enfoque bien pensado para usar ganchos con Firestore. Sin embargo, no puedo entender el código.

Cloné esto, e intenté agregar todos los archivos públicos y luego, cuando lo ejecuté, en realidad no puedo hacer que el código funcione. No estoy seguro de lo que falta en las instrucciones sobre cómo hacer que esto se ejecute para ver si hay lecciones en el código que puedan ayudar a resolver este problema.

PRÓXIMO INTENTO

Compré la plantilla divjoy, que se anuncia como configurada para firebase (no está configurada para firefire en caso de que alguien más esté considerando esto como una opción).

Esa plantilla propone un contenedor de autenticación que inicializa la configuración de la aplicación, pero solo para los métodos de autenticación, por lo que debe reestructurarse para permitir otro proveedor de contexto para el almacén de incendios. Cuando sales de ese proceso y usas el proceso que se muestra en esta publicación , lo que queda es un error en la siguiente devolución de llamada:

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

No sabe qué es firebase. Esto se debe a que está definido en el proveedor de contexto de Firebase que se importa y define (en la función useProvideAuth ()) como:

  const firebase = useContext(FirebaseContext)

Sin posibilidades de devolución de llamada, el error dice:

React Hook useEffect tiene una dependencia que falta: 'firebase'. Inclúyalo o elimine la matriz de dependencias

O, si intento agregar esa constante a la devolución de llamada, aparece un error que dice:

No se puede llamar a React Hook "useContext" dentro de una devolución de llamada. Los React Hooks deben llamarse en un componente de función React o en una función personalizada React Hook

PRÓXIMO INTENTO

He reducido mi archivo de configuración de Firebase a solo variables de configuración (escribiré ayudantes en los proveedores de contexto para cada contexto que quiera usar).

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;  

Luego tengo un proveedor de contexto de autenticación de la siguiente manera:

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

También hice un proveedor de contexto de Firebase de la siguiente manera:

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

Luego, en index.js envuelvo la aplicación en el proveedor de Firebase

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

serviceWorker.unregister();

y en mi lista de rutas, he incluido las rutas relevantes en el proveedor de autenticación:

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;

En este intento particular, vuelvo al problema marcado anteriormente con este error:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth no es una función

Señala que esta línea del proveedor de autenticación crea el problema:

useEffect(() => {

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

Intenté usar F mayúscula en Firebase y genera el mismo error.

Cuando pruebo el consejo de Tristan, elimino todas esas cosas e intento definir mi método de cancelación de suscripción como un método no listado (no sé por qué no está usando el lenguaje firebase, pero si su enfoque funcionara, me esforzaría más) para averiguar por qué). Cuando trato de usar su solución, el mensaje de error dice:

TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ default (...) no es una función

La respuesta a esta publicación sugiere eliminar () de después de la autenticación. Cuando intento eso, aparece un error que dice:

TypeError: no se puede leer la propiedad 'onAuthStateChanged' de undefined

Sin embargo, esta publicación sugiere un problema con la forma en que se importa firebase en el archivo de autenticación.

Lo tengo importado como: importar Firebase desde "../firebase";

Firebase es el nombre de la clase.

Los videos recomendados por Tristan son antecedentes útiles, pero actualmente estoy en el episodio 9 y todavía no encuentro la parte que se supone que ayuda a resolver este problema. ¿Alguien sabe dónde encontrar eso?

SIGUIENTE INTENTO A continuación, y tratando de resolver solo el problema de contexto, importé createContext y useContext e intenté usarlos como se muestra en esta documentación.

No puedo pasar un error que dice:

Error: llamada de enlace no válida. Los ganchos solo se pueden llamar dentro del cuerpo de un componente de función. Esto podría suceder por una de las siguientes razones: ...

He leído las sugerencias en este enlace. para tratar de resolver este problema y no puedo resolverlo. No tengo ninguno de los problemas que se muestran en esta guía de solución de problemas.

Actualmente, la declaración de contexto tiene el siguiente aspecto:

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

Pasé tiempo usando este curso udemy para tratar de descubrir el contexto y el elemento de enganche a este problema, después de verlo, el único aspecto de la solución propuesta por Tristan a continuación es que el método createContext no se llama correctamente en su publicación. necesita ser "React.createContext" pero aún no se acerca a resolver el problema.

Todavía estoy atascado.

¿Alguien puede ver lo que salió mal aquí?

Mel
fuente
No está definido porque no lo está importando.
Josh Pittman
3
no sólo tiene que añadir exporta export const FirebaseContext?
Federkun
Hola Mel, pido disculpas por la lenta respuesta, ¡he tenido dos semanas locas en el trabajo, así que mirar una computadora por la noche estaba fuera de discusión! He actualizado mi respuesta para que pueda proporcionar una solución limpia y muy simple que pueda verificar.
Tristan Trainer
Muchas gracias, lo miraré ahora.
Mel
Hola Mel, acabo de actualizar aún más con la implementación correcta de las actualizaciones en tiempo real de la tienda de incendios también (puede eliminar la parte de onSnapshot para que no sea en tiempo real) Si esta es la solución, ¿puedo sugerir que posiblemente actualice su pregunta para que sea una mucho más corto y conciso, por lo que otros que lo vean también pueden encontrar la solución, gracias, perdón por la naturaleza lenta de las respuestas
Tristan Trainer

Respuestas:

12

Edición mayor : Tomé un tiempo para analizar esto un poco más, esto es lo que se me ocurrió, es una solución más limpia, alguien podría estar en desacuerdo conmigo acerca de que esta es una buena manera de abordar esto.

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 es nulo, no se autentica, si el usuario tiene un valor, se autentica.

firebaseConfigse puede encontrar en la consola de Firebase => Configuración del proyecto => Aplicaciones => Botón de opción de configuración

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

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

Este enlace useEffect es el núcleo para rastrear los cambios de autenticación de un usuario. Agregamos un escucha al evento onAuthStateChanged de firebase.auth () que actualiza el valor de authUser. Este método devuelve una devolución de llamada para cancelar la suscripción de este escucha que podemos usar para limpiar el escucha cuando se actualiza el enlace useFirebase.

Este es el único gancho que necesitamos para la autenticación de Firebase (se pueden hacer otros ganchos para el almacén de incendios, 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>
}

Esta es una implementación básica del Appcomponente de 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 }
}

Esto se puede implementar en su componente así:

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

}

Esto elimina la necesidad de React useContext a medida que recibimos actualizaciones directamente de Firebase.

Observe un par de cosas:

  1. Guardar un documento sin cambios no desencadena una nueva instantánea, por lo que el "ahorro excesivo" no causa reenvíos
  2. Al llamar a getDocument, la devolución de llamada onUpdate se llama de inmediato con una "instantánea" inicial, por lo que no necesita un código adicional para obtener el estado inicial del documento.

Editar ha eliminado una gran parte de la respuesta anterior

Tristan Trainer
fuente
1
gracias por esto. No puedo ver cómo has configurado esto. Con el proveedor, recibo un error que dice que createContext no está definido. Eso es porque no hay consumidor hasta ahora. ¿Dónde has puesto el tuyo?
Mel
Hola, lo siento createContext es parte de react, así que impórtalo en la parte superior como {createContext} de 'react'. Me di cuenta de que olvidé mostrar a dónde va el proveedor de Firebase, editaré la respuesta
Tristan Trainer
Lo importé en el proveedor, pero se vuelve indefinido. Creo que es porque no hay consumidor para eso
Mel
1
El consumidor es el enlace useContext (), pero al mirar su pregunta nuevamente, parece que no está exportando FirebaseContext desde el archivo, es por eso que no puede encontrar el contexto :)
Tristan Trainer
1
Hola @Mel, gracias, es muy amable, espero que sea útil al final. Hooks y Firebase están bastante involucrados y me tomó mucho tiempo entenderlo y es posible que no haya encontrado la mejor solución incluso ahora, podría crear un tutorial sobre mi enfoque en algún momento, ya que es más fácil de explicar a medida que codifica eso.
Tristan Trainer
4

Firebase no está definido porque no lo está importando. Primero, debe ser firebase.firestore()como se muestra en el ejemplo en los documentos que vinculó a https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore . Luego, debe importar realmente Firebase en el archivo, por lo import * as firebase from 'firebase';que se describe en el archivo Léame del paquete Firebase https://www.npmjs.com/package/firebase

Josh Pittman
fuente
1
Lo estoy importando en index.js
Mel
1
ReactDOM.render (<FirebaseContext.Provider value = {new Firebase ()}> <App /> </FirebaseContext.Provider>, document.getElementById ('root'));
Mel
1
Es por eso que el enfoque funciona con componentDidMount
Mel
1
WithAuth HOC también está envuelto en withFirebase.
Mel
3
Pero su error dice que no está definido. Te estoy ayudando a definirlo, y no me estás diciendo si la solución funciona o cuál es el error resultante. Siempre haces que sea muy difícil ayudarte, Mel. Mi punto es que lo está importando en un archivo que no sea el archivo del componente dashboard2, que es donde lo referencia, lo que está causando el error. Inmortalizar algo en el índice no ayuda a su compilación a comprender qué es en un archivo completamente diferente.
Josh Pittman
2

EDITAR (3 de marzo de 2020):

Comencemos desde cero.

  1. He creado un nuevo proyecto:

    hilo crear react-app firebase-hook-issue

  2. Eliminé los 3 archivos App * creados de forma predeterminada, eliminé la importación de index.js y también eliminé al trabajador de servicio para tener un index.js limpio así:

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. ¡He comenzado la aplicación para ver que Hello Firebase! está impreso
  2. He agregado el módulo Firebase
yarn add firebase
  1. Ejecuté firebase init para configurar firebase para ese proyecto. Elegí uno de mis proyectos de Firebase vacíos y seleccioné Base de datos y Almacén de incendios, que terminan creando los siguientes archivos:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. He añadido las importaciones de librerías base de fuego y también creó un Firebase clase y FirebaseContext . Por fin, envolví la aplicación en el componente FirebaseContext.Provider y configuré su valor en una nueva instancia de Firebase () . Esto es, vamos a tener solo una instancia de la aplicación Firebase instanciada como deberíamos porque debe ser 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. Verifiquemos que podamos leer cualquier cosa de Firestore. Para verificar solo la lectura primero, fui a mi proyecto en Firebase Console, abrí mi base de datos de Cloud Firestore y agregué una nueva colección llamada contadores con un documento simple que contiene un campo llamado valor de tipo número y valor 0. ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

  2. Luego actualicé la clase de la aplicación para usar FirebaseContext que creamos, usé el gancho useState para nuestro gancho contador simple y usé el gancho useEffect para leer el valor del almacén de incendios:

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

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

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

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

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

const FirebaseContext = React.createContext(null);

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

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

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

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

Nota: Para mantener la respuesta lo más breve posible, me he asegurado de que no necesite autenticarse con firebase, configurando el acceso a firestore para que esté en modo de prueba (archivo 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);
    }
  }
}

Mi respuesta anterior: eres más que bienvenido a echar un vistazo a mi react-firebase-auth-skeleton:

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

Sigue principalmente el artículo:

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

Pero algo reescrito para usar ganchos. Lo he usado en al menos dos de mis proyectos.

Uso típico de mi proyecto actual de mascotas:

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

Las dos partes más importantes aquí son: - gancho useAuthUser () que proporciona al usuario autenticado actual. - FirebaseContext que utilizo a través del gancho useContext .

const firebase = useContext(FirebaseContext);

Cuando tiene contexto para firebase, depende de usted implementar el objeto firebase a su gusto. A veces escribo algunas funciones útiles, a veces es más fácil configurar los oyentes directamente en el gancho useEffect que creo para mi componente actual.

Una de las mejores partes de ese artículo fue la creación de withAuthorization HOC, que le permite especificar los requisitos previos para acceder a la página, ya sea en el componente mismo:

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

O tal vez incluso configure esas condiciones directamente en la implementación de su enrutador.

Espero que mirar el repositorio y el artículo le brinde algunos buenos pensamientos adicionales para mejorar otras respuestas brillantes a su pregunta.

fxdxpz
fuente
Compré su libro y seguí su enfoque. Encontré que el enfoque de las condiciones no funcionaba realmente cuando se implementó y que el protocolo de autenticación establecido en ese libro no pudo mantener el estado a través de las actualizaciones de componentes. No he encontrado una manera de usar lo que se establece en ese libro. Gracias de todos modos por compartir tus pensamientos.
Mel
No estoy seguro que quieres decir. ¿Has probado mi aplicación esqueleto con tu proyecto firebase? Todas las condiciones funcionan hasta donde puedo ver, porque lo he estado usando en al menos 3 proyectos.
fxdxpz