Estou tentando descobrir como usar um ouvinte do Firebase para que os dados do firestore na nuvem sejam atualizados com atualizações de ganchos de reação.
Inicialmente, fiz isso usando um componente de classe com uma função componentDidMount para obter os dados do 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,
});
}
Isso é interrompido quando a página é atualizada, então estou tentando descobrir como mover o ouvinte para reagir aos ganchos.
Instalei a ferramenta react-firebase-hooks - embora não consiga descobrir como ler as instruções para poder fazê-la funcionar.
Eu tenho um componente de função da seguinte maneira:
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 é agrupado em um wrapper authUser no nível da rota, da seguinte maneira:
<Route path={ROUTES.DASHBOARD2} render={props => (
<AuthUserContext.Consumer>
{ authUser => (
<Dashboard2 authUser={authUser} {...props} />
)}
</AuthUserContext.Consumer>
)} />
Eu tenho um arquivo firebase.js, que se conecta ao firestore da seguinte maneira:
class Firebase {
constructor() {
app.initializeApp(config).firestore();
/* helpers */
this.fieldValue = app.firestore.FieldValue;
/* Firebase APIs */
this.auth = app.auth();
this.db = app.firestore();
}
Ele também define um ouvinte para saber quando o authUser é alterado:
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;
}
});
};
Então, na minha configuração de contexto do firebase, tenho:
import FirebaseContext, { withFirebase } from './Context';
import Firebase from '../../firebase';
export default Firebase;
export { FirebaseContext, withFirebase };
O contexto é configurado em um wrapper withFirebase da seguinte maneira:
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;
Então, no meu HOC de autenticação, tenho um provedor 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;
Atualmente - quando tento isso, recebo um erro no componente Dashboard2 que diz:
Firebase 'não está definido
Tentei firebase em minúsculas e recebi o mesmo erro.
Eu também tentei firebase.firestore e Firebase.firestore. Eu recebo o mesmo erro.
Gostaria de saber se não posso usar meu HOC com um componente de função?
Eu já vi esse aplicativo de demonstração e esta postagem no blog .
Seguindo os conselhos do blog, criei um novo firebase / contextReader.jsx com:
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
}
Em seguida, tento envolver meu App.jsx nesse leitor com:
function App() {
const { initializing, user } = useAuth()
if (initializing) {
return <div>Loading</div>
}
// )
// }
// const App = () => (
return (
<userContext.Provider value={{ user }}>
<Router>
<Navigation />
<Route path={ROUTES.LANDING} exact component={StandardLanding} />
Quando tento isso, recebo um erro que diz:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth não é uma função
Eu vi esse post lidando com esse erro e tentei desinstalar e reinstalar o fio. Não faz diferença.
Quando olho para o aplicativo de demonstração , ele sugere que o contexto deve ser criado usando um método de 'interface'. Não consigo ver de onde vem isso - não consigo encontrar uma referência para explicá-lo na documentação.
Não consigo entender as instruções além de tentar o que fiz para conectar isso.
Eu já vi esse post que tenta ouvir o firestore sem usar o react-firebase-hooks. As respostas apontam para a tentativa de descobrir como usar essa ferramenta.
Eu li esta excelente explicação que explica como se afastar dos HOCs para ganchos. Estou empolgado com como integrar o ouvinte do firebase.
Eu já vi esse post, que fornece um exemplo útil de como pensar em fazer isso. Não tenho certeza se devo tentar fazer isso no authListener componentDidMount - ou no componente Dashboard que está tentando usá-lo.
PRÓXIMA TENTATIVA Encontrei este post , que está tentando resolver o mesmo problema.
Quando tento implementar a solução oferecida por Shubham Khatri, instalei a configuração do firebase da seguinte maneira:
Um provedor de contexto com: import React, {useContext} de 'react'; importar Firebase de '../../firebase';
const FirebaseContext = React.createContext();
export const FirebaseProvider = (props) => (
<FirebaseContext.Provider value={new Firebase()}>
{props.children}
</FirebaseContext.Provider>
);
O gancho de contexto possui:
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;
Em seguida, no index.js, envolvo o aplicativo no provedor 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();
Então, quando tento usar o ouvinte no componente, tenho:
import React, {useContext} from 'react';
import { FirebaseContext } from '../Firebase/ContextHookProvider';
import useFirebaseAuthentication from '../Firebase/ContextHook';
const Dashboard2 = (props) => {
const firebase = useContext(FirebaseContext);
const authUser =
useFirebaseAuthentication(firebase);
return (
<div>authUser.email</div>
)
}
export default Dashboard2;
E eu tento usá-lo como uma rota sem componentes ou wrapper de autenticação:
<Route path={ROUTES.DASHBOARD2} component={Dashboard2} />
Quando tento isso, recebo um erro que diz:
Tentativa de erro de importação: 'FirebaseContext' não é exportado de '../Firebase/ContextHookProvider'.
Essa mensagem de erro faz sentido, porque ContextHookProvider não exporta o FirebaseContext - exporta o FirebaseProvider - mas se eu não tentar importar isso no Dashboard2 - não posso acessá-lo na função que tenta usá-lo.
Um efeito colateral dessa tentativa é que meu método de inscrição não funciona mais. Agora ele gera uma mensagem de erro que diz:
TypeError: Não é possível ler a propriedade 'doCreateUserWithEmailAndPassword' de null
Vou resolver esse problema mais tarde - mas deve haver uma maneira de descobrir como usar o reagir com firebase que não envolva meses desse ciclo por milhões de avenidas que não funcionam para obter uma configuração básica de autenticação. Existe um kit inicial para firebase (firestore) que funcione com ganchos de reação?
Na próxima tentativa , tentei seguir a abordagem deste curso udemy - mas só funciona para gerar uma entrada de formulário - não há um ouvinte para contornar as rotas para ajustar com o usuário autenticado.
Eu tentei seguir a abordagem neste tutorial do youtube - que tem esse repositório para trabalhar. Ele mostra como usar ganchos, mas não como usar o contexto.
PRÓXIMA TENTATIVA Encontrei este repositório que parece ter uma abordagem bem pensada para usar ganchos com firestore. No entanto, não consigo entender o código.
Eu clonei isso - e tentei adicionar todos os arquivos públicos e, quando o executo -, na verdade, não consigo fazer com que o código funcione. Não tenho certeza do que está faltando nas instruções de como executar isso para verificar se há lições no código que podem ajudar a resolver esse problema.
PRÓXIMA TENTATIVA
Comprei o modelo divjoy, anunciado como sendo configurado para o firebase (não é configurado para o firestore no caso de alguém mais considerar isso como uma opção).
Esse modelo propõe um wrapper de autenticação que inicializa a configuração do aplicativo - mas apenas para os métodos de autenticação -, portanto, ele precisa ser reestruturado para permitir outro provedor de contexto para o firestore. Quando você percorre esse processo e usa o processo mostrado nesta postagem , resta um erro no seguinte retorno de chamada:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Não sabe o que é o firebase. Isso ocorre porque é definido no provedor de contexto do firebase que é importado e definido (na função useProvideAuth ()) como:
const firebase = useContext(FirebaseContext)
Sem chances para o retorno de chamada, o erro diz:
O React Hook useEffect tem uma dependência ausente: 'firebase'. Inclua-o ou remova a matriz de dependência
Ou, se eu tentar adicionar essa const ao retorno de chamada, recebo um erro que diz:
O gancho de reação "useContext" não pode ser chamado dentro de um retorno de chamada. Ganchos de reação devem ser chamados em um componente da função React ou em uma função personalizada do React Hook
PRÓXIMA TENTATIVA
Reduzi meu arquivo de configuração do firebase para apenas variáveis de configuração (escreverei ajudantes nos provedores de contexto para cada contexto que desejar 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;
Em seguida, tenho um provedor de contexto de autenticação da seguinte maneira:
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];
};
Também fiz um provedor de contexto de base de firmas da seguinte maneira:
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>
)
}
Em index.js, envolvo o aplicativo no provedor da base de firmas
ReactDom.render(
<FirebaseProvider>
<App />
</FirebaseProvider>,
document.getElementById("root"));
serviceWorker.unregister();
e na minha lista de rotas, agrupei as rotas relevantes no provedor de autenticação:
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;
Nesta tentativa em particular, estou de volta ao problema sinalizado anteriormente com este erro:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth não é uma função
Aponta para esta linha do provedor de autenticação como criando o problema:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Eu tentei usar F maiúsculo no Firebase e ele gera o mesmo erro.
Quando tento o conselho de Tristan, removo todas essas coisas e tento definir meu método de cancelamento de inscrição como um método que não está listado (não sei por que ele não está usando a linguagem firebase - mas se a abordagem dele funcionasse, eu tentaria mais para descobrir o porquê). Quando tento usar sua solução, a mensagem de erro diz:
TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ padrão (...) não é uma função
A resposta a esta postagem sugere remover () após a autenticação. Quando tento isso, recebo um erro que diz:
TypeError: Não é possível ler a propriedade 'onAuthStateChanged' de undefined
No entanto, este post sugere um problema com a maneira como o firebase é importado no arquivo auth.
Eu o importei como: import Firebase from "../firebase";
Firebase é o nome da classe.
Os vídeos recomendados por Tristan são úteis, mas atualmente estou no episódio 9 e ainda não encontrei a parte que deveria ajudar a resolver esse problema. Alguém sabe onde encontrar isso?
NEXT ATTEMPT Em seguida - e tentando resolver apenas o problema de contexto - importei createContext e useContext e tentei usá-los conforme mostrado nesta documentação.
Não consigo passar um erro que diz:
Erro: chamada inválida. Ganchos só podem ser chamados dentro do corpo de um componente de função. Isso pode acontecer por um dos seguintes motivos: ...
Passei pelas sugestões neste link para tentar resolver esse problema e não consigo descobrir. Não tenho nenhum dos problemas mostrados neste guia de solução de problemas.
Atualmente - a declaração de contexto é a seguinte:
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>
);
Passei um tempo usando este curso da udemy para tentar descobrir o contexto e o elemento de gancho para esse problema - depois de assistir - o único aspecto da solução proposta por Tristan abaixo é que o método createContext não é chamado corretamente em seu post. ele precisa ser "React.createContext", mas ainda não chega nem perto de resolver o problema.
Eu ainda estou preso.
Alguém pode ver o que deu errado aqui?
export
a export const FirebaseContext
?