Слухач Firebase з React Hooks


27

Я намагаюся розібратися, як використовувати слухач Firebase, щоб дані хмарного firestore були оновлені оновленнями реактивних гаків.

Спочатку я робив це, використовуючи компонент класу з функцією компонентDidMount, щоб отримати дані 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,
  });  
}

Це руйнується, коли сторінка оновлюється, тому я намагаюся зрозуміти, як перенести слухача на реакцію на гачки.

Я встановив інструмент " гаки-гачки" , хоча я не можу зрозуміти, як прочитати інструкції, щоб змусити його працювати.

У мене є функціональний компонент наступним чином:

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

Цей компонент загортається в обгортку авторизації на рівні маршруту таким чином:

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

У мене є файл firebase.js, який підключається до firestore таким чином:

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


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


  }

Він також визначає слухача, який повинен знати, коли змінюється автор:

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

Потім у моєму контекстному налаштуванні Firebase я маю:

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

Контекст налаштовується в обгорткуFirebase наступним чином:

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;

Тоді в моєму інструменті з аутентифікацією HOC у мене є постачальник контексту як:

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;

В даний час - коли я намагаюся це, я отримую помилку в компоненті Dashboard2, який говорить:

Firebase 'не визначено

Я спробував Firebase малої літери і отримав таку ж помилку.

Я також спробував firebase.firestore та Firebase.firestore. Я отримую ту ж помилку.

Мені цікаво, чи не можу я використовувати свій HOC з функціональним компонентом?

Я бачив цю демонстраційну програму та цю публікацію в блозі .

Дотримуючись поради в блозі, я створив нову firebase / contextReader.jsx з:

 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
  }  

Тоді я спробую завернути свій App.jsx у цей читач із:

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

Коли я намагаюся це, я отримую помилку, яка говорить:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth не є функцією

Я бачив, як ця публікація займається цією помилкою і спробувала видалити та перевстановити пряжу. Це не має ніякої різниці.

Коли я дивлюся на демонстраційну програму , це говорить про те, що контекст слід створювати за допомогою методу "інтерфейс". Я не бачу, звідки це походить - я не можу знайти посилання, щоб пояснити це в документації.

Я не можу мати сенсу інших інструкцій, крім того, щоб спробувати те, що я зробив, щоб підключити цю програму.

Я бачив цю публікацію, яка намагається слухати firestore без використання гаків react-firebase. Відповіді вказують на спробу розібратися, як користуватися цим інструментом.

Я прочитав це чудове пояснення, яке стосується того, як перейти від ГОС до гачків. Я застряг у тому, як інтегрувати слухач Firebase.

Я бачив цю публікацію, яка є корисним прикладом, як думати про це. Не впевнений, чи потрібно мені намагатися це зробити в authListener компонентDidMount - або в компоненті панелі інструментів, який намагається це використовувати.

НАЙКРАЩА АТЕМПТ Я знайшов цю посаду , яка намагається вирішити ту саму проблему.

Коли я намагаюся реалізувати рішення, запропоноване Shubham Khatri, я налаштував конфігурацію firebase так:

Контекстний постачальник з: import React, {useContext} з 'react'; імпортувати Firebase з '../../firebase';

const FirebaseContext = React.createContext(); 

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

Потім контекстний гак має:

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;

Потім я вказую додаток у провайдера як:

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

Потім, коли я намагаюся використовувати слухача в компоненті, у мене є:

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;

І я намагаюся використовувати його як маршрут без компонентів або автентичної обгортки:

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

Коли я намагаюся це, я отримую помилку, яка говорить:

Спроба імпорту імпорту: 'FirebaseContext' не експортується з '../Firebase/ContextHookProvider'.

Це повідомлення про помилку має сенс, оскільки ContextHookProvider не експортує FirebaseContext - він експортує FirebaseProvider - але якщо я не намагаюся імпортувати це в Dashboard2 - то я не можу отримати доступ до нього у функції, яка намагається його використовувати.

Одним із побічних ефектів цієї спроби є те, що метод моєї реєстрації більше не працює. Тепер воно генерує повідомлення про помилку, яке говорить:

TypeError: Неможливо прочитати властивість 'doCreateUserWithEmailAndPassword' з null

Я вирішу цю проблему пізніше, але повинен бути спосіб зрозуміти, як використовувати реакцію з firebase, яка не передбачає місяці цього циклу через мільйони проспектів, які не працюють, щоб отримати базову настройку авторизації. Чи є стартовий комплект для пожежної бази (firestore), який працює з гачками реагування?

Наступна спроба я спробував дотримуватися підходу в цьому курсі udemy - але це працює лише для формування вводу форми - немає слухача, який би розміщував маршрути для налаштування з автентифікованим користувачем.

Я намагався дотримуватися підходу в цьому навчальному посібнику ютуб - з якого над цим репо потрібно працювати. Він показує, як використовувати гачки, але не як використовувати контекст.

НАСТУПНИЙ НАСТУП Я знайшов це репо, яке, здається, має добре продуманий підхід до використання гачків з вогнем. Однак я не можу зрозуміти код.

Я клонував це - і спробував додати всі загальнодоступні файли, а потім, коли його запускаю - насправді не можу отримати код для роботи. Я не впевнений, чого не вистачає в інструкціях, як змусити це запустити, щоб перевірити, чи є уроки в коді, які можуть допомогти вирішити цю проблему.

НАСЛІДЧИЙ НАСТУП

Я купив шаблон divjoy, який рекламується як налаштування для firebase (це не налаштування для firestore у випадку, якщо хтось інший розглядає це як варіант).

Цей шаблон пропонує обгортку автентичності, яка ініціалізує конфігурацію програми, але лише для методів аутентифікації, тому її потрібно реструктурувати, щоб дозволити інший постачальник контексту для firestore. Коли ви заблукаєте через цей процес і використовуєте процес, показаний у цій публікації , що залишилось - це помилка в наступному зворотному дзвінку:

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

Він не знає, що таке firebase. Це тому, що він визначений у контексті постачальника Firebase, який імпортується та визначається (у функції useProvideAuth ()) як:

  const firebase = useContext(FirebaseContext)

Без шансів на зворотний виклик помилка говорить:

React Hook useEffect має відсутність залежності: "firebase". Або включіть його, або видаліть масив залежності

Або якщо я спробую додати цей const до зворотного дзвінка, я отримаю помилку, яка говорить:

Не можна викликати внутрішній зворотний дзвінок "useContext" React Hook. Реактивні гачки повинні викликатися в компоненті функції React або в спеціальній функції React Hook

НАСЛІДЧИЙ НАСТУП

Я скоротив свій конфігураційний файл firebase до просто конфігураційних змінних (я напишу помічників у контекстних провайдерів для кожного контексту, який я хочу використовувати).

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;  

Потім у мене є постачальник аутентичних контекстів таким чином:

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

Я також зробив контекстний постачальник Firebase наступним чином:

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

Потім в index.js я загортаю додаток у постачальника Firebase

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

serviceWorker.unregister();

і в моєму списку маршрутів я вклав відповідні маршрути в постачальника аутентифікації:

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;

З цієї конкретної спроби я повернувся до проблеми, позначеної раніше з цією помилкою:

TypeError: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth не є функцією

Він вказує на цей рядок постачальника авторів як на створення проблеми:

useEffect(() => {

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

Я спробував використовувати з великої літери F у Firebase, і це генерує ту ж помилку.

Коли я спробую поради Тристана, я видаляю всі ці речі і намагаюсь визначити свій метод відписки як метод "unlisten" (я не знаю, чому він не використовує мову firebase - але якщо його підхід спрацював, я б постарався більше розібратися, чому). Коли я намагаюся використовувати його рішення, повідомлення про помилку говорить:

TypeError: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ за замовчуванням (...) не є функцією

Відповідь на це повідомлення пропонує видалити () з моменту авт. Коли я намагаюся це, я отримую помилку, яка говорить:

TypeError: Неможливо прочитати властивість "onAuthStateChanged" невизначеного

Однак ця публікація передбачає проблему із способом імпортування firebase у файл auth.

У мене це імпорт як: імпорт Firebase з "../firebase";

Firebase - це назва класу.

Відео, рекомендовані Трістаном, є корисним фоном, але зараз я перебуваю в епізоді 9, і досі не знайдено тієї частини, яка повинна допомогти вирішити цю проблему. Хтось знає, де це знайти?

Наступна ATTEMPT Далі - і намагаюся вирішити лише контекстну проблему - я імпортував і createContext, і useContext, і намагався використовувати їх, як показано в цій документації.

Я не можу пройти помилку, яка говорить:

Помилка: недійсний дзвінок на гачок. Гачки можна назвати лише всередині корпусу функціонального компонента. Це може статися з однієї з наступних причин: ...

Я переглянув пропозиції в цьому посиланні, щоб спробувати вирішити цю проблему, і не можу її вирішити. У мене немає жодних проблем, показаних у цьому посібнику зі зйомки неполадок.

Наразі - контекстна заява виглядає так:

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

Я витратив час, використовуючи цей курс udemy, щоб спробувати розібратися в контексті та гаках елементу цієї проблеми - після його перегляду - єдиним аспектом рішення, запропонованим Трістаном нижче, є те, що метод createContext не називається правильно на своєму посту. це має бути "React.createContext", але він все ще не наближається до вирішення проблеми.

Я все ще застряг.

Хто-небудь може бачити, що тут зникло?


Це не визначено, оскільки ви не імпортуєте його.
Джош Піттман

3
Ви просто потрібно додати exportдо export const FirebaseContext?
Федеркун

Привіт Мел, вибач за повільну відповідь, у мене був божевільний два тижні роботи, тому дивитись на комп’ютер увечері не було сумніву! Я оновив свою відповідь для вас, щоб забезпечити чисте і дуже просте рішення, яке ви можете перевірити.
Тренер Трістан

Велике спасибі - я зараз погляну на це.
Мел

Привіт Мел, щойно оновлюється правильною реалізацією оновлень у реальному часі також з firestore (можна видалити частину onSnapshot, щоб зробити її не в режимі реального часу) Якщо це рішення, будь ласка, я можу запропонувати можливо оновити ваше питання, щоб зробити його набагато коротше та більш лаконічне, тому інші, хто переглядає це, теж можуть знайти рішення, дякую - знову вибачте за повільний характер відповідей
тренер Tristan

Відповіді:


12

Основна редакція : Узяв трохи часу, щоб розібратися в цьому трохи більше, це те, що я придумав - це більш чисте рішення, хтось може не погодитися зі мною щодо того, що це хороший спосіб підійти до цього.

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 }

Якщо authUser є нульовим, то він не автентифікований, якщо користувач має значення, то автентифіковано.

firebaseConfigможна знайти на консолі firebase => Налаштування проекту => Apps => Налаштувати кнопку радіо

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

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

Цей гак useEffect є основою для відстеження authChanges користувача. Ми додаємо слухача до події onAuthStateChanged firebase.auth (), яка оновлює значення authUser. Цей метод повертає зворотний виклик для відписки цього слухача, який ми можемо використати для очищення слухача, коли гачок useFirebase буде оновлений.

Це єдиний гак, який нам потрібен для аутентифікації Firebase (інші гачки можна зробити для firestore тощо).

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("name@email.com", "password0");
  }

  return <div>
    <label>User is not Authenticated</label>
    <button onClick={handleLogin}>Log In</button>
  </div>
}

Це основна реалізація Appкомпонента acreate-react-app

використовувати гачок бази даних Firestore

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

Це можна реалізувати у вашому компоненті так:

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

}

Потім це усуває необхідність React useContext, оскільки ми отримуємо оновлення безпосередньо з самої бази даних.

Помітьте кілька речей:

  1. Збереження незмінного документа не спричиняє новий знімок, тому "прострочення" не спричиняє повторного відображення
  2. При виклику getDocument зворотний виклик onUpdate викликається відразу з початковим "знімком", тому вам не потрібен додатковий код для отримання початкового стану документа.

Правка видалила великий фрагмент старої відповіді


1
спасибі за це. Я не бачу, як ти це налаштував. З провайдером я отримую помилку, яка говорить, що createContext не визначений. Це тому, що поки немає споживача. Куди ти поставив своє?
Мел

Привіт вибачте createContext є частиною реакції, тому імпортуйте її вгорі, оскільки {createContext} з 'react' я зрозумів, що забув показати, куди йде провайдер Firebase, я відредагую відповідь
Tristan Trainer

Я імпортував його в провайдера, але він не визначається. Я думаю, що це тому, що для нього немає споживача
Мел

1
Споживач - гачок useContext (), але, дивлячись на ваше запитання ще раз, схоже, ви не експортуєте FirebaseContext з файлу - тому він не може знайти контекст :)
Tristan Trainer

1
Привіт @Mel спасибі, що це дуже добре, я сподіваюся, що це виявиться корисним врешті. Гачки та Firebase обидва залучені, і мені знадобилося багато часу, щоб обернутися, і я, можливо, не знайшов найкращого рішення навіть зараз, я можу створити підручник щодо мого підходу деякий час, оскільки це простіше пояснити, як ви кодуєте це.
Тренер Трістан

4

Firebase не визначено, оскільки ви не імпортуєте її. По-перше, його потрібно вказати firebase.firestore()у прикладі на документах, які ви пов’язали з https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore . Тоді вам потрібно фактично імпортувати Firebase у файл, так, import * as firebase from 'firebase';як зазначено в пакеті readme https://www.npmjs.com/package/firebase


1
Я імпортую його в index.js
Мел

1
ReactDOM.render (<FirebaseContext.Provider value = {new Firebase ()}> <App /> </FirebaseContext.Provider>, document.getElementById ('root'));
Мел

1
Ось чому підхід працює з компонентомDidMount
Мел

1
WithAuth HOC також загорнутий уFirebase.
Мел

3
Але ваша помилка говорить, що вона не визначена. Я допомагаю вам визначити це, і ви не говорите мені, чи рішення працює, або внаслідок чого виникає помилка. Ви завжди дуже важко допомагаєте Мелу. Моя думка полягає в тому, що ви імпортуєте його у файл, відмінний від компонентного файлу dashboard2, саме там ви посилаєтесь на нього, що викликає помилку. Введення чогось в індекс не допомагає вашій збірці зрозуміти, що це в абсолютно іншому файлі.
Джош Піттман

2

EDIT (3 березня 2020 р.):

Почнемо з нуля.

  1. Я створив новий проект:

    пряжа створити реагувати-додаток firebase-гак-питання

  2. Я видалив усі 3 файли App *, створені за замовчуванням, видалив імпорт з index.js, а також видалив службового працівника, щоб мати чистий index.js, як:

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. Я запустив додаток, щоб побачити, що Hello Firebase! роздруковується.
  2. Я додав модуль firebase
yarn add firebase
  1. Я запустив firebase init, щоб налаштувати firebase для цього проекту. Я вибрав один із моїх порожніх проектів firebase, і я вибрав Database і Firestore, які в кінцевому підсумку створюють наступні файли:
.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
  1. Я додав імпорту для firebase LIBS , а також створив Firebase клас і FirebaseContext . Нарешті я завернув додаток у компонент FirebaseContext.Provider і встановив його значення новому екземпляру Firebase () . Це ми мали мати лише один екземпляр програми Firebase, як слід, оскільки це повинен бути сингл:
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. Давайте перевіримо, що ми можемо прочитати що-небудь із Firestore. Щоб перевірити лише читання спочатку, я зайшов до свого проекту в Firebase Console, відкрив базу даних Cloud Firestore і додав нову колекцію під назвою лічильники з простим документом, що містить одне поле під назвою значення типу типу та значення 0. введіть тут опис зображення введіть тут опис зображення

  2. Потім я оновив клас додатків для використання створеного нами FirebaseContext, використовував держак-гачок для нашого простого лічильника і використовував useEffect-гак для зчитування значення з firestore:

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

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

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

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

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

const FirebaseContext = React.createContext(null);

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

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

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

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

Примітка: Щоб зберегти відповідь якомога коротше, я переконався, що вам не потрібно автентифікувати за допомогою firebase, встановивши доступ до firestore, щоб бути в тестовому режимі (файл 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);
    }
  }
}

Моя попередня відповідь: Ви більше ніж ласкаво погляньте на мій скелет react-firebase-auth:

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

Це головним чином випливає із статті:

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

Але дещо переписано для використання гачків. Я використав це принаймні у двох своїх проектах.

Типове використання мого поточного проекту для домашніх тварин:

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

Дві найважливіші частини тут: - гак useAuthUser (), який забезпечує поточного аутентифікованого користувача. - FirebaseContext, який я використовую через гачок useContext .

const firebase = useContext(FirebaseContext);

Коли у вас є контекст для firebase, вам належить реалізувати об'єкт firebase на свій смак. Іноді я пишу кілька корисних функцій, іноді простіше просто налаштувати слухачів прямо у гачку useEffect, яку я створюю для мого поточного компонента.

Однією з найкращих частин цієї статті було створення aAuthorization HOC, що дозволяє вказати передумови доступу до сторінки або в самому компоненті:

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

Або, можливо, навіть налаштувати ці умови прямо у вашій реалізації маршрутизатора.

Сподіваємось, що перегляд репо та статті дасть вам кілька гарних думок для покращення інших блискучих відповідей на ваше запитання.


Я купив його книгу і дотримувався його підходу. Я виявив, що підхід щодо умов насправді не спрацьовує, коли він реалізований, і що протокол автентичності, викладений у цій книзі, не зміг підтримувати статус за допомогою оновлення компонентів. Я не знайшов способу використовувати те, що викладено в цій книзі. Все одно дякую за те, що поділилися своїми думками.
Мел

Я не впевнений, що ти маєш на увазі. Ви спробували мій додаток для скелета з вашим проектом firebase? Наскільки я можу сказати, усі умови працюють, тому що я використовував це принаймні у 3 проектах.
fxdxpz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.