Próbuję wymyślić, jak korzystać z nasłuchiwania Firebase, aby dane firestore w chmurze były odświeżane za pomocą aktualizacji hooków reagowania.
Początkowo zrobiłem to przy użyciu komponentu klasy z funkcją componentDidMount, aby uzyskać dane firestore.
this.props.firebase.db
.collection('users')
// .doc(this.props.firebase.db.collection('users').doc(this.props.firebase.authUser.uid))
.doc(this.props.firebase.db.collection('users').doc(this.props.authUser.uid))
.get()
.then(doc => {
this.setState({ name: doc.data().name });
// loading: false,
});
}
To się psuje, gdy strona się aktualizuje, więc próbuję wymyślić, jak przesunąć słuchacza, aby zareagował na haki.
Zainstalowałem narzędzie hooks reagowania na ogień - chociaż nie mogę wymyślić, jak przeczytać instrukcje, aby móc go uruchomić.
Mam składnik funkcji w następujący sposób:
import React, { useState, useEffect } from 'react';
import { useDocument } from 'react-firebase-hooks/firestore';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
useRouteMatch,
} from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import { compose } from 'recompose';
import { withFirebase } from '../Firebase/Index';
import { AuthUserContext, withAuthorization, withEmailVerification, withAuthentication } from '../Session/Index';
function Dashboard2(authUser) {
const FirestoreDocument = () => {
const [value, loading, error] = useDocument(
Firebase.db.doc(authUser.uid),
//firebase.db.doc(authUser.uid),
//firebase.firestore.doc(authUser.uid),
{
snapshotListenOptions: { includeMetadataChanges: true },
}
);
return (
<div>
<p>
{error && <strong>Error: {JSON.stringify(error)}</strong>}
{loading && <span>Document: Loading...</span>}
{value && <span>Document: {JSON.stringify(value.data())}</span>}
</p>
</div>
);
}
}
export default withAuthentication(Dashboard2);
Ten komponent jest zawinięty w opakowanie authUser na poziomie trasy w następujący sposób:
<Route path={ROUTES.DASHBOARD2} render={props => (
<AuthUserContext.Consumer>
{ authUser => (
<Dashboard2 authUser={authUser} {...props} />
)}
</AuthUserContext.Consumer>
)} />
Mam plik firebase.js, który podłącza się do firestore w następujący sposób:
class Firebase {
constructor() {
app.initializeApp(config).firestore();
/* helpers */
this.fieldValue = app.firestore.FieldValue;
/* Firebase APIs */
this.auth = app.auth();
this.db = app.firestore();
}
Definiuje także detektor, aby wiedzieć, kiedy zmienia się authUser:
onAuthUserListener(next, fallback) {
// onUserDataListener(next, fallback) {
return this.auth.onAuthStateChanged(authUser => {
if (authUser) {
this.user(authUser.uid)
.get()
.then(snapshot => {
let snapshotData = snapshot.data();
let userData = {
...snapshotData, // snapshotData first so it doesn't override information from authUser object
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerifed,
providerData: authUser.providerData
};
setTimeout(() => next(userData), 0); // escapes this Promise's error handler
})
.catch(err => {
// TODO: Handle error?
console.error('An error occured -> ', err.code ? err.code + ': ' + err.message : (err.message || err));
setTimeout(fallback, 0); // escapes this Promise's error handler
});
};
if (!authUser) {
// user not logged in, call fallback handler
fallback();
return;
}
});
};
Następnie w mojej konfiguracji kontekstowej bazy ogniowej mam:
import FirebaseContext, { withFirebase } from './Context';
import Firebase from '../../firebase';
export default Firebase;
export { FirebaseContext, withFirebase };
Kontekst jest konfigurowany w opakowaniu z opcją FireFase w następujący sposób:
import React from 'react';
const FirebaseContext = React.createContext(null);
export const withFirebase = Component => props => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
export default FirebaseContext;
Następnie w moim HOC z uwierzytelnianiem mam dostawcę kontekstu jako:
import React from 'react';
import { AuthUserContext } from '../Session/Index';
import { withFirebase } from '../Firebase/Index';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: null,
};
}
componentDidMount() {
this.listener = this.props.firebase.auth.onAuthStateChanged(
authUser => {
authUser
? this.setState({ authUser })
: this.setState({ authUser: null });
},
);
}
componentWillUnmount() {
this.listener();
};
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withFirebase(WithAuthentication);
};
export default withAuthentication;
Obecnie - kiedy próbuję tego, pojawia się błąd w komponencie Dashboard2, który mówi:
Baza ogniowa ”nie jest zdefiniowana
Próbowałem małych firebase i otrzymałem ten sam błąd.
Próbowałem także firebase.firestore i Firebase.firestore. Otrzymuję ten sam błąd.
Zastanawiam się, czy nie mogę używać mojego HOC ze składnikiem funkcji?
Widziałem tę aplikację demonstracyjną i ten post na blogu .
Postępując zgodnie z radami na blogu, stworzyłem nowy firebase / contextReader.jsx za pomocą:
import React, { useEffect, useContext } from 'react';
import Firebase from '../../firebase';
export const userContext = React.createContext({
user: null,
})
export const useSession = () => {
const { user } = useContext(userContext)
return user
}
export const useAuth = () => {
const [state, setState] = React.useState(() =>
{ const user = firebase.auth().currentUser
return { initializing: !user, user, }
}
);
function onChange(user) {
setState({ initializing: false, user })
}
React.useEffect(() => {
// listen for auth state changes
const unsubscribe = firebase.auth().onAuthStateChanged(onChange)
// unsubscribe to the listener when unmounting
return () => unsubscribe()
}, [])
return state
}
Następnie próbuję owinąć App.jsx w tym czytniku:
function App() {
const { initializing, user } = useAuth()
if (initializing) {
return <div>Loading</div>
}
// )
// }
// const App = () => (
return (
<userContext.Provider value={{ user }}>
<Router>
<Navigation />
<Route path={ROUTES.LANDING} exact component={StandardLanding} />
Gdy próbuję tego, pojawia się komunikat o błędzie:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth nie jest funkcją
Widziałem ten post dotyczący tego błędu i próbowałem odinstalować i ponownie zainstalować przędzę. To nie robi różnicy.
Kiedy patrzę na aplikację demonstracyjną , sugeruje to, że kontekst powinien zostać utworzony przy użyciu metody „interfejsu”. Nie widzę, skąd to pochodzi - nie mogę znaleźć odniesienia do wyjaśnienia tego w dokumentacji.
Nie mogę zrozumieć instrukcji poza próbowaniem tego, co zrobiłem, aby to podłączyć.
Widziałem ten post, który próbuje słuchać sklepu z ogniem bez użycia haczyków reagujących na ogień. Odpowiedzi wskazują na próbę wymyślenia, jak korzystać z tego narzędzia.
Przeczytałem to doskonałe wyjaśnienie, które opisuje, jak przejść od HOC do haków. Utknąłem z tym, jak zintegrować moduł nasłuchujący Firebase.
Widziałem ten post, który stanowi pomocny przykład, jak pomyśleć o zrobieniu tego. Nie jestem pewien, czy powinienem to zrobić w komponencie authListenerDidMount - czy w komponencie Dashboard, który próbuje go użyć.
NASTĘPNA PRÓBA Znalazłem ten post , który próbuje rozwiązać ten sam problem.
Kiedy próbuję wdrożyć rozwiązanie oferowane przez Shubham Khatri, konfiguruję konfigurację firebase w następujący sposób:
Dostawca kontekstu z: import React, {useContext} z „reaguj”; import Firebase z „../../firebase”;
const FirebaseContext = React.createContext();
export const FirebaseProvider = (props) => (
<FirebaseContext.Provider value={new Firebase()}>
{props.children}
</FirebaseContext.Provider>
);
Hak kontekstowy ma wtedy:
import React, { useEffect, useContext, useState } from 'react';
const useFirebaseAuthentication = (firebase) => {
const [authUser, setAuthUser] = useState(null);
useEffect(() =>{
const unlisten =
firebase.auth.onAuthStateChanged(
authUser => {
authUser
? setAuthUser(authUser)
: setAuthUser(null);
},
);
return () => {
unlisten();
}
});
return authUser
}
export default useFirebaseAuthentication;
Następnie w pliku index.js pakuję aplikację do dostawcy jako:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App/Index';
import {FirebaseProvider} from './components/Firebase/ContextHookProvider';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<FirebaseProvider>
<App />
</FirebaseProvider>,
document.getElementById('root')
);
serviceWorker.unregister();
Następnie, gdy próbuję użyć detektora w komponencie, mam:
import React, {useContext} from 'react';
import { FirebaseContext } from '../Firebase/ContextHookProvider';
import useFirebaseAuthentication from '../Firebase/ContextHook';
const Dashboard2 = (props) => {
const firebase = useContext(FirebaseContext);
const authUser =
useFirebaseAuthentication(firebase);
return (
<div>authUser.email</div>
)
}
export default Dashboard2;
I próbuję użyć go jako trasy bez składników lub opakowania uwierzytelniania:
<Route path={ROUTES.DASHBOARD2} component={Dashboard2} />
Gdy próbuję tego, pojawia się komunikat o błędzie:
Próba błędu importu: „FirebaseContext” nie jest eksportowany z „../Firebase/ContextHookProvider”.
Ten komunikat o błędzie ma sens, ponieważ ContextHookProvider nie eksportuje FirebaseContext - eksportuje FirebaseProvider - ale jeśli nie spróbuję zaimportować tego w Dashboard2 - nie będę mógł uzyskać do niego dostępu w funkcji, która próbuje go użyć.
Jednym z efektów ubocznych tej próby jest to, że moja metoda rejestracji już nie działa. Teraz generuje komunikat o błędzie:
TypeError: Nie można odczytać właściwości „doCreateUserWithEmailAndPassword” o wartości null
Rozwiążę ten problem później - ale musi istnieć sposób, aby dowiedzieć się, jak używać reagowania z bazą ogniową, która nie wymaga miesięcy tej pętli przez miliony ścieżek, które nie działają, aby uzyskać podstawową konfigurację uwierzytelniania. Czy istnieje zestaw startowy do bazy ogniowej (firestore), który współpracuje z hakami reagującymi?
Podczas następnej próby próbowałem podążać za tym podejściem w tym udemy - ale działa to tylko w celu wygenerowania formularza - nie ma słuchacza, który mógłby ominąć trasy, aby dostosować się do uwierzytelnionego użytkownika.
Starałem się postępować zgodnie z tym podejściem w tym samouczku youtube - który ma to repozytorium do pracy. Pokazuje, jak używać haczyków, ale nie jak używać kontekstu.
NASTĘPNA PRÓBA Znalazłem to repozytorium, które wydaje się mieć dobrze przemyślane podejście do używania haków ze sklepem ogniowym. Nie mogę jednak zrozumieć kodu.
Sklonowałem to - i próbowałem dodać wszystkie pliki publiczne, a następnie po uruchomieniu - nie mogę tak naprawdę uruchomić kodu. Nie jestem pewien, czego brakuje w instrukcjach, jak to uruchomić, aby sprawdzić, czy w kodzie są lekcje, które mogą pomóc rozwiązać ten problem.
NASTĘPNA PRÓBA
Kupiłem szablon divjoy, który jest reklamowany jako konfigurowany dla firebase (nie jest konfigurowany dla firestore na wypadek, gdyby ktokolwiek rozważał to jako opcję).
Ten szablon proponuje opakowanie uwierzytelniające, które inicjuje konfigurację aplikacji - ale tylko dla metod uwierzytelniania - więc należy go zrestrukturyzować, aby umożliwić innemu dostawcy kontekstu dla magazynu ognia. Gdy przejdziesz przez ten proces i użyjesz procesu pokazanego w tym poście , pozostanie błąd w następującym wywołaniu zwrotnym:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Nie wie, co to jest baza ogniowa. Jest tak, ponieważ jest zdefiniowany w dostawcy kontekstu bazy ogniowej, który jest importowany i definiowany (w funkcji useProvideAuth ()) jako:
const firebase = useContext(FirebaseContext)
Bez szans na oddzwonienie błąd mówi:
React use HookEffect ma brakującą zależność: „firebase”. Dołącz go lub usuń tablicę zależności
Lub jeśli spróbuję dodać tę const do wywołania zwrotnego, pojawia się błąd:
Nie można wywołać haka React „useContext” wewnątrz wywołania zwrotnego. Haczyki React muszą być wywoływane w komponencie funkcji React lub niestandardowej funkcji React Hook
NASTĘPNA PRÓBA
Zmniejszyłem mój plik konfiguracyjny bazy ogniowej do samych zmiennych konfiguracyjnych (napiszę pomocników u dostawców kontekstu dla każdego kontekstu, którego chcę użyć).
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
const devConfig = {
apiKey: process.env.REACT_APP_DEV_API_KEY,
authDomain: process.env.REACT_APP_DEV_AUTH_DOMAIN,
databaseURL: process.env.REACT_APP_DEV_DATABASE_URL,
projectId: process.env.REACT_APP_DEV_PROJECT_ID,
storageBucket: process.env.REACT_APP_DEV_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_DEV_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_DEV_APP_ID
};
const prodConfig = {
apiKey: process.env.REACT_APP_PROD_API_KEY,
authDomain: process.env.REACT_APP_PROD_AUTH_DOMAIN,
databaseURL: process.env.REACT_APP_PROD_DATABASE_URL,
projectId: process.env.REACT_APP_PROD_PROJECT_ID,
storageBucket: process.env.REACT_APP_PROD_STORAGE_BUCKET,
messagingSenderId:
process.env.REACT_APP_PROD_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_PROD_APP_ID
};
const config =
process.env.NODE_ENV === 'production' ? prodConfig : devConfig;
class Firebase {
constructor() {
firebase.initializeApp(config);
this.firebase = firebase;
this.firestore = firebase.firestore();
this.auth = firebase.auth();
}
};
export default Firebase;
Następnie mam dostawcę kontekstu uwierzytelniania w następujący sposób:
import React, { useState, useEffect, useContext, createContext } from "react";
import Firebase from "../firebase";
const authContext = createContext();
// Provider component that wraps app and makes auth object ...
// ... available to any child component that calls useAuth().
export function ProvideAuth({ children }) {
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}
// Hook for child components to get the auth object ...
// ... and update when it changes.
export const useAuth = () => {
return useContext(authContext);
};
// Provider hook that creates auth object and handles state
function useProvideAuth() {
const [user, setUser] = useState(null);
const signup = (email, password) => {
return Firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(response => {
setUser(response.user);
return response.user;
});
};
const signin = (email, password) => {
return Firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(response => {
setUser(response.user);
return response.user;
});
};
const signout = () => {
return Firebase
.auth()
.signOut()
.then(() => {
setUser(false);
});
};
const sendPasswordResetEmail = email => {
return Firebase
.auth()
.sendPasswordResetEmail(email)
.then(() => {
return true;
});
};
const confirmPasswordReset = (password, code) => {
// Get code from query string object
const resetCode = code || getFromQueryString("oobCode");
return Firebase
.auth()
.confirmPasswordReset(resetCode, password)
.then(() => {
return true;
});
};
// Subscribe to user on mount
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
// Subscription unsubscribe function
return () => unsubscribe();
}, []);
return {
user,
signup,
signin,
signout,
sendPasswordResetEmail,
confirmPasswordReset
};
}
const getFromQueryString = key => {
return queryString.parse(window.location.search)[key];
};
Stworzyłem również kontekstowego dostawcę bazy ogniowej w następujący sposób:
import React, { createContext } from 'react';
import Firebase from "../../firebase";
const FirebaseContext = createContext(null)
export { FirebaseContext }
export default ({ children }) => {
return (
<FirebaseContext.Provider value={ Firebase }>
{ children }
</FirebaseContext.Provider>
)
}
Następnie w index.js zawijam aplikację do dostawcy firebase
ReactDom.render(
<FirebaseProvider>
<App />
</FirebaseProvider>,
document.getElementById("root"));
serviceWorker.unregister();
i na mojej liście tras zawinąłem odpowiednie trasy w dostawcy uwierzytelniania:
import React from "react";
import IndexPage from "./index";
import { Switch, Route, Router } from "./../util/router.js";
import { ProvideAuth } from "./../util/auth.js";
function App(props) {
return (
<ProvideAuth>
<Router>
<Switch>
<Route exact path="/" component={IndexPage} />
<Route
component={({ location }) => {
return (
<div
style={{
padding: "50px",
width: "100%",
textAlign: "center"
}}
>
The page <code>{location.pathname}</code> could not be found.
</div>
);
}}
/>
</Switch>
</Router>
</ProvideAuth>
);
}
export default App;
Po tej konkretnej próbie wracam do problemu oznaczonego wcześniej tym błędem:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth nie jest funkcją
Wskazuje ten wiersz dostawcy uwierzytelnienia jako stwarzający problem:
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
Próbowałem użyć wielkiej litery F w Firebase i generuje ten sam błąd.
Kiedy wypróbowuję rady Tristana, usuwam wszystkie te rzeczy i próbuję zdefiniować moją metodę rezygnacji z subskrypcji jako metodę nie słuchania (nie wiem, dlaczego on nie używa języka bazy ogniowej - ale jeśli jego podejście zadziałałoby, postaram się bardziej dowiedzieć się, dlaczego). Gdy próbuję użyć jego rozwiązania, komunikat o błędzie mówi:
TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ domyślnie (...) nie jest funkcją
Odpowiedź na ten post sugeruje usunięcie () po auth. Gdy próbuję, pojawia się komunikat o błędzie:
TypeError: Nie można odczytać właściwości „onAuthStateChanged” niezdefiniowanej
Jednak ten post sugeruje problem ze sposobem importowania bazy ogniowej do pliku auth.
Mam go zaimportowany jako: import Firebase z „../firebase”;
Firebase to nazwa klasy.
Filmy zalecane przez Tristana są pomocne w tle, ale obecnie jestem w odcinku 9 i nadal nie znalazłem części, która powinna pomóc rozwiązać ten problem. Czy ktoś wie, gdzie to znaleźć?
NASTĘPNA PRÓBKA Następnie - i próbując rozwiązać tylko problem kontekstowy - zaimportowałem zarówno createContext, jak i useContext i próbowałem ich użyć, jak pokazano w tej dokumentacji.
Nie mogę dostać błędu, który mówi:
Błąd: nieprawidłowe połączenie hakowe. Haczyki można wywoływać tylko wewnątrz ciała komponentu funkcji. Może się to zdarzyć z jednego z następujących powodów: ...
Przeszedłem przez sugestie zawarte w tym linku, aby spróbować rozwiązać ten problem i nie mogę go rozwiązać. Nie mam żadnych problemów pokazanych w tym przewodniku rozwiązywania problemów.
Obecnie - instrukcja kontekstu wygląda następująco:
import React, { useContext } from 'react';
import Firebase from "../../firebase";
export const FirebaseContext = React.createContext();
export const useFirebase = useContext(FirebaseContext);
export const FirebaseProvider = props => (
<FirebaseContext.Provider value={new Firebase()}>
{props.children}
</FirebaseContext.Provider>
);
Spędziłem czas, wykorzystując ten kurs udemy, aby dowiedzieć się, jaki jest kontekst i element tego problemu - po jego obejrzeniu - jedynym aspektem rozwiązania zaproponowanego przez Tristana poniżej jest to, że metoda createContext nie została poprawnie wywołana w jego poście. musi to być „React.createContext”, ale nadal nie jest blisko rozwiązania problemu.
Nadal utknąłem.
Czy ktoś może zobaczyć, co poszło nie tak?
export
do export const FirebaseContext
?