Firebase слушатель с React Hooks

27

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

Первоначально я сделал это, используя компонент класса с функцией componentDidMount, чтобы получить данные о пожарном депо.

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

Это ломается, когда страница обновляется, поэтому я пытаюсь понять, как заставить слушателя реагировать на хуки.

Я установил инструмент Reaction-Firebase-Hooks - хотя я не могу понять, как читать инструкции, чтобы заставить его работать.

У меня есть компонент функции следующим образом:

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

Этот компонент упакован в оболочку authUser на уровне маршрута следующим образом:

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


  }

Он также определяет слушателя, который будет знать, когда изменяется 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;
        }
    });
  };

Затем в моей настройке контекста FireBase у меня есть:

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

Контекст настраивается в оболочке withFirebase следующим образом:

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;

Затем в моем withAuthentication 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.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} />

Когда я пытаюсь это сделать, я получаю сообщение об ошибке:

Ошибка типа: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth не является функцией

Я видел этот пост с этой ошибкой и пытался удалить и переустановить пряжу. Это не имеет значения.

Когда я смотрю на демонстрационное приложение , оно предполагает, что контекст должен быть создан с использованием метода 'interface'. Я не вижу, откуда это исходит - я не могу найти ссылку, чтобы объяснить это в документации.

Я не могу понять инструкции, кроме как попробовать то, что я сделал, чтобы подключить это.

Я видел этот пост, в котором делается попытка прослушать firestore без использования активных-firebase-hooks. Ответы указывают на попытку выяснить, как использовать этот инструмент.

Я прочитал это превосходное объяснение, которое объясняет, как перейти от HOCs к крючкам. Я застрял с тем, как интегрировать слушатель Firebase.

Я видел этот пост, который предоставляет полезный пример того, как думать об этом. Не уверен, что я должен пытаться сделать это в компоненте authListenerDidMount - или в компоненте Dashboard, который пытается его использовать.

СЛЕДУЮЩАЯ ПОПЫТКА Я нашел этот пост , который пытается решить ту же проблему.

Когда я пытаюсь реализовать решение, предлагаемое Шубхамом Хатри, я настраивал конфигурацию firebase следующим образом:

Поставщик контекста с: import React, {useContext} из 'response'; импортировать 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;

Затем в index.js я обернуть приложение в провайдере как:

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, которая не включает месяцы этого цикла через миллионы путей, которые не работают для получения базовой настройки аутентификации. Есть ли стартовый набор для firebase (firestore), который работает с реактивными крючками?

Следующая попытка Я попытался следовать подходу в этом курсе udemy - но он работает только для генерации ввода формы - нет слушателя, который бы разбирал маршруты для настройки с аутентифицированным пользователем.

Я попытался следовать подходу в этом уроке на YouTube - с которым работает этот репозиторий . Он показывает, как использовать хуки, но не как использовать контекст.

СЛЕДУЮЩАЯ ПОПЫТКА Я нашел этот репозиторий, который, кажется, имеет хорошо продуманный подход к использованию хуков с firestore. Тем не менее, я не могу понять код.

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

СЛЕДУЮЩАЯ ПОПЫТКА

Я купил шаблон divjoy, который рекламируется как настройка для firebase (он не настроен для firestore в случае, если кто-то еще рассматривает это как вариант).

Этот шаблон предлагает обертку аутентификации, которая инициализирует конфигурацию приложения - но только для методов аутентификации - поэтому его необходимо реструктурировать, чтобы позволить другому провайдеру контекста для firestore. Когда вы запутываетесь в этом процессе и используете процесс, показанный в этом посте , остается ошибка в следующем обратном вызове:

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

Он не знает, что такое пожарная база. Это потому, что он определен в провайдере контекста firebase, который импортируется и определяется (в функции useProvideAuth ()) как:

  const firebase = useContext(FirebaseContext)

Без шансов на обратный вызов, ошибка говорит:

У React Hook useEffect отсутствует зависимость: 'firebase'. Либо включите его, либо удалите массив зависимостей

Или, если я пытаюсь добавить это const к обратному вызову, я получаю сообщение об ошибке:

React Hook «useContext» не может быть вызван внутри обратного вызова. React Hooks должны вызываться в компоненте функции React или в пользовательской функции React Hooks.

СЛЕДУЮЩАЯ ПОПЫТКА

Я сократил мой файл конфигурации 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;

На этой конкретной попытке я возвращаюсь к проблеме, помеченной ранее с этой ошибкой:

Ошибка типа: _firebase__WEBPACK_IMPORTED_MODULE_2 __. Default.auth не является функцией

Это указывает на эту строку поставщика аутентификации как создающую проблему:

useEffect(() => {

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

Я попытался использовать заглавную букву F в Firebase, и она выдает ту же ошибку.

Когда я пользуюсь советом Тристана, я удаляю все эти вещи и пытаюсь определить свой метод отмены подписки как метод отсутствия доступа (я не знаю, почему он не использует язык Firebase - но если бы его подход работал, я бы постарался больше выяснить почему). Когда я пытаюсь использовать его решение, появляется сообщение об ошибке:

Ошибка типа: _util_contexts_Firebase__WEBPACK_IMPORTED_MODULE_8 ___ default (...) не является функцией

Ответ на этот пост предлагает удалить () после аутентификации. Когда я пытаюсь это сделать, я получаю сообщение об ошибке:

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

Однако этот пост предлагает проблему со способом импорта firebase в файле auth.

Я импортировал его как: импорт Firebase из "../firebase";

Firebase это название класса.

Видео, рекомендованные Тристаном, являются полезной информацией, но я в настоящее время нахожусь в эпизоде ​​9 и все еще не нашел ту часть, которая должна помочь решить эту проблему. Кто-нибудь знает, где это найти?

СЛЕДУЮЩАЯ ПОПЫТКА Далее - и пытаясь решить только проблему контекста - я импортировал как 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, чтобы она не работала в режиме реального времени). Если это решение, могу ли я предложить обновить свой вопрос, чтобы сделать его Гораздо короче и лаконичнее, чтобы другие, кто его просматривал, тоже могли найти решение, спасибо - еще раз извините за медленный характер ответов
Тристан Трейнер

Ответы:

12

Major Edit : Потребовалось некоторое время, чтобы разобраться в этом немного подробнее, вот что я придумала, это более чистое решение, кто-то может не согласиться со мной по поводу того, что это хороший способ приблизиться к этому.

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 имеет значение null, то не аутентифицируется, если у пользователя есть значение, то аутентифицируется.

firebaseConfigможно найти в консоли Firebase => Настройки проекта => Приложения => Переключатель Config

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компонентаcreate-react-app

Использование базы данных Hirestore 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 }
}

Это может быть реализовано в вашем компоненте следующим образом:

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, поскольку мы получаем обновления непосредственно от самой базы Firebase.

Обратите внимание на пару вещей:

  1. Сохранение неизмененного документа не вызывает нового снимка, поэтому «перерасход» не вызывает повторных визуализаций
  2. При вызове getDocument обратный вызов onUpdate вызывается сразу с начальным «снимком», поэтому вам не нужен дополнительный код для получения начального состояния документа.

Редактировать удалил большой кусок старого ответа

Тристан Трейнер
источник
1
Спасибо за это. Я не вижу, как ты это настроил. С провайдером я получаю ошибку, которая говорит, что createContext не определен. Это потому, что пока нет потребителя. Куда ты положил свой?
Мел
Привет, извините, createContext является частью реакции, поэтому импортируйте его вверху как {createContext} из 'реакции'. Я понял, что забыл показать, куда идет поставщик Firebase, я отредактирую ответ
Tristan Trainer
Я импортировал его в провайдера, но он отображает неопределенное значение. Я думаю, это потому, что для этого нет потребителя
Мел
1
Потребитель - это ловушка useContext (), но, глядя на ваш вопрос еще раз, похоже, что вы не экспортируете FirebaseContext из файла - вот почему он не может найти контекст :)
Tristan Trainer
1
Привет, @Mel, спасибо, что очень любезен, надеюсь, в конечном итоге это окажется полезным. Hooks и Firebase достаточно вовлечены, и мне потребовалось много времени, чтобы разобраться с этим, и я, возможно, не нашел лучшего решения даже сейчас, я мог бы создать учебник по моему подходу через некоторое время, так как это легче объяснить, когда вы пишете код Это.
Тристан Тренер
4

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

Джош Питтман
источник
1
Я импортирую его в index.js
Мел
1
ReactDOM.render (<FirebaseContext.Provider value = {new Firebase ()}> <App /> </FirebaseContext.Provider>, document.getElementById ('root'));
Мел
1
Вот почему этот подход работает с componentDidMount
Мел
1
WithAuth HOC также включен в FireBase.
Мел
3
Но ваша ошибка говорит, что она не определена. Я помогаю вам определить его, а вы не говорите мне, работает ли решение или в чем заключается ошибка. Тебе всегда очень трудно помочь тебе, Мел. Я хочу сказать, что вы импортируете его в файл, отличный от файла компонента dashboard2, на который вы ссылаетесь, что вызывает ошибку. Импортирование чего-либо в индекс не поможет вашей сборке понять, что это в совершенно другом файле.
Джош Питтман
2

РЕДАКТИРОВАТЬ (3 марта 2020 г.):

Давайте начнем с нуля.

  1. Я создал новый проект:

    пряжа создать ответную реакцию

  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. Я запустил приложение, чтобы увидеть, что Привет 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, а также создал класс 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 и добавил новую коллекцию с именем counters с простым документом, содержащим одно поле с именем value number и value 0. введите описание изображения здесь введите описание изображения здесь

  2. Затем я обновил класс App для использования созданного нами FirebaseContext, сделал хук useState для нашего простого счетчика хуков и использовал хук 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);
    }
  }
}

Мой предыдущий ответ: не стесняйтесь взглянуть на мой response-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, который я создаю для моего текущего компонента.

Одной из лучших частей этой статьи было создание withAuthorization HOC, который позволяет указать предварительные условия для доступа к странице либо в самом компоненте:

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

Или, может быть, даже настройки этих условий прямо в реализации вашего маршрутизатора.

Надеюсь, что просмотр репо и статьи даст вам несколько хороших мыслей, чтобы улучшить другие блестящие ответы на ваш вопрос.

fxdxpz
источник
Я купил его книгу и следовал его подходу. Я обнаружил, что подход условий фактически не работал, когда реализован, и что протокол проверки подлинности, изложенный в этой книге, не смог поддерживать статус посредством обновлений компонентов. Я не нашел способа использовать то, что изложено в этой книге. В любом случае, спасибо, что поделились своими мыслями.
Мел
Я не совсем понимаю, что вы имеете ввиду. Вы пробовали мое скелетное приложение с вашим проектом Firebase? Все условия работают, насколько я могу судить, потому что я использовал его по крайней мере в 3 проектах.
fxdxpz